0

I'm seeing some strange race conditions in my webapp that I suspect may be due to the Entity Framework handling read locks in an unexpected way. When a request is made in my application to any page, I automatically load up the account model which is then stored in my DbContext for the lifetime of the request. Some web pages need to lock the account DB row so I can safely do some other operations without race conditions. Here's how I'm doing this now...

//... code that begins the request and loads the account into context.
// Some pages may run code that looks something like this.
using(var tran = existingCtx.Database.BeginTransaction(IsolationLevel.RepeatableRead))
{
    // Lock customer.
    var act = ctx.Accounts.Find(purchaseFor.ID);
    if (act == null)
        throw new RecordNotFoundException("Unable to find specified customer.");

    DoStuffRelyingOnLock();
    Commit();
}

Will the call find Find(purchaseFor.ID) LOCK the account row in the database even if it's already loaded into context?

jbq
  • 181
  • 3
  • 4
  • 13

2 Answers2

3

Will the call find Find(purchaseFor.ID) LOCK the account row in the database even if it's already loaded into context?

No, it won't. If the context already has that entity loaded, it won't even talk to the database.

See what the documentation says about how the Find method works (emphasis mine):

Finding entities using primary keys

The Find method on DbSet uses the primary key value to attempt to find an entity tracked by the context. If the entity is not found in the context then a query will be sent to the database to find the entity there. Null is returned if the entity is not found in the context or in the database.

Find is different from using a query in two significant ways:

  • A round-trip to the database will only be made if the entity with the given key is not found in the context.
  • Find will return entities that are in the Added state. That is, Find will return entities that have been added to the context but have not yet been saved to the database.

Therefore, if you want to ensure that a query to the database is always made for your locking purposes, avoid using Find, and use the Where LINQ method instead.

Community
  • 1
  • 1
sstan
  • 35,425
  • 6
  • 48
  • 66
2

It is good practice that if you are going to handle transactions explicitly, you have your code either commit or rollback. The MSDN documentation indicates that the explicit rollback is required, but the using statement will fire the Dispose() method, which in turn will rollback any open transaction. For clarity, especially as code tends to get more complex, it is better to handle the rollback explicitly.

This OpenStack question has a more thorough explanation in the accepted answer.

Community
  • 1
  • 1
btberry
  • 377
  • 1
  • 7
  • 2
    Is there not an implicit rollback? If not, what's the point of the transaction implementing `IDisposable`? – Blorgbeard Aug 29 '16 at 23:38
  • See the mdsn documentation [link](https://msdn.microsoft.com/en-us/data/dn456843.aspx) to see the using statement and proper transaction handling together in the context of EF. – btberry Aug 29 '16 at 23:44
  • 1
    Yeah, what you are suggesting is good practice. But it's completely false to say that OP's code will leave a transaction open. The transaction instance will get disposed at the end of the `using` block. This includes calling rollback if the transaction is still open by then. – sstan Aug 29 '16 at 23:52
  • You are correct @sstan, I was going off the MS documentation from the link I provided above: "You take responsibility for committing or rolling back the transaction and for calling Dispose() on it, as well as for closing and disposing the database connection." They immediately give an example where they have an explicit rollback in a catch. I did some more digging and found their rollback is superfluous because they do have the rollback covered by the Dispose() which gets triggered by the using statement. – btberry Aug 30 '16 at 00:02