1

Given a TransactionScope with 2 subsequently opened DbContexts, are changes saved by the first context guaranteed to be visible within the scope of second context?

var txOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
using (var transaction = new TransactionScope(TransactionScopeOption.Required, txOptions))
{
    using (var context1 = new MyDbContext())
    {
        context1.Employees.Single(e => e.Id == 1).Salary += 1; // Update
        context1.Employees.Remove(context1.Employees.Single(e => e.Id == 2)); // Delete
        context1.Employees.Add(new Employee { Id = 3 }); // Add

        context1.SaveChanges();
    }

    using (var context2 = new MyDbContext())
    {
        // are changes saved by context1 guaranteed to be visible here?
    }

    transaction.Complete();
}

Normally I would expect they are, but then I saw "No, this was a coincidence because the 2nd context reused the connection of the 1st from the connection pool. This is not guaranteed and will break under load." which made me puzzled. Could anyone please confirm or disprove this?

Vladi Pavelka
  • 916
  • 4
  • 12

1 Answers1

1

My understanding is that all efforts will be made to ensure that your thread receives the same connection from the pool, provided that you close each connection prior to requesting another, and provided that the connection strings are identical. Reusing the same connection will prevent the transaction from escalating to DTC.

However if you still want further guarantees, there is an overload of the DbContext constructor which takes an existing connection. In this way, you would be able to guarantee that the two Contexts use the same Connection. This way you don't need to worry about the behaviour of the lightweight transaction manager. IMO you'd be best off opening your own connection, and passing this to both contexts, with the contextOwnsConnection flag set to false.

(Hopefully the below is all Hypothetical)

The consequences of not reusing the connection are dire - if context2 was not able to reuse the same connection as context1, the bounding TransactionScope would escalate into a distributed transaction, which would in any case cause further potential lock + deadlock issues.

i.e. in your example, the 3 rows associated with the modified employees in context1 would be blocked to a second database connection on context2 with a ReadCommited isolation level, until the Transaction is either committed, or rolled back (i.e. your program could hang until the transaction, or command, timed out).

I guess one burning question - since both contexts use the same database, if possible, try to combine the two contexts. This way you can avoid the bounding TransactionScope altogether - SaveChanges() acts as a single phase transaction against a single connection.

StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • 1
    Thanks for your answer Stuart! : ) I'm trying to reason about an existing code in my current job, where this approach with TxScope + multiple dbContexts is widely used. If there is a chance for it to break later in production, it needs to be fixed/refactored. Surely, If it were me making the decision, I would go for just a single DbContext - which is already a business transaction on it's own. – Vladi Pavelka Jun 15 '18 at 09:28
  • 1
    It's been a while since I worked on this but I think "all efforts being made" means that it's going to work 99% of the time except in production under load, then likely bringing your application down. It works 100% on your machine during testing. – usr Jun 15 '18 at 15:08