实体框架事务锁定

Posted

技术标签:

【中文标题】实体框架事务锁定【英文标题】:Entity Framework Transaction Locking 【发布时间】:2017-01-06 02:21:53 【问题描述】:

我在我的 web 应用程序中看到一些奇怪的竞争条件,我怀疑这可能是由于实体框架以意想不到的方式处理读取锁。当我的应用程序向任何页面发出请求时,我会自动加载帐户模型,然后在请求的生命周期内将其存储在我的 DbContext 中。一些网页需要锁定帐户数据库行,这样我就可以安全地进行一些其他操作而无需竞争条件。这就是我现在的做法......

//... 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();

调用是否会找到 Find(purchaseFor.ID) LOCK 数据库中的帐户行,即使它已经加载到上下文中?

【问题讨论】:

【参考方案1】:

调用是否会找到 Find(purchaseFor.ID) LOCK 数据库中的帐户行,即使它已经加载到上下文中?

不,不会。如果上下文已经加载了该实体,它甚至不会与数据库对话。

查看documentation 对Find 方法如何工作的说明(重点是我的):

使用主键查找实体

DbSet 上的 Find 方法使用主键值来尝试查找上下文跟踪的实体。 如果在上下文中未找到实体,则将向数据库发送查询以查找那里的实体。 如果在上下文中未找到实体,则返回 Null或在数据库中。

Find 与使用查询有两个显着不同:

只有在上下文中找不到具有给定键的实体时才会往返于数据库。 Find 将返回处于 Added 状态的实体。也就是说,Find 将返回已添加到上下文但尚未保存到数据库的实体。

因此,如果您想确保始终出于锁定目的对数据库进行查询,请避免使用Find,而是使用Where LINQ 方法。

【讨论】:

【参考方案2】:

如果您要显式处理事务,最好让您的代码要么 提交 回滚。 MSDN documentation 表示需要显式回滚,但 using 语句将触发 Dispose() 方法,该方法反过来将回滚任何打开的事务。为清楚起见,尤其是当代码趋于变得更复杂时,最好显式处理回滚。

这个OpenStack question在接受的答案中有更详尽的解释。

【讨论】:

没有隐式回滚吗?如果不是,那么实现IDisposable 的事务有什么意义? 查看 mdsn 文档 link 以查看 EF 上下文中的 using 语句和正确的事务处理。 是的,你的建议是好的做法。但是说 OP 的代码会让交易保持打开状态是完全错误的。事务实例将在using 块的末尾处理。这包括如果到那时事务仍然打开,则调用回滚。 你是对的@sstan,我从上面提供的链接中关闭了 MS 文档:“你负责提交或回滚事务并在其上调用 Dispose()至于关闭和处理数据库连接。”他们立即给出了一个例子,他们在 catch 中有一个明确的回滚。我做了更多的挖掘,发现他们的回滚是多余的,因为他们确实有由使用语句触发的 Dispose() 覆盖的回滚。

以上是关于实体框架事务锁定的主要内容,如果未能解决你的问题,请参考以下文章

如何正确锁定和重新加载实体

JPA 和乐观锁定模式

实体框架事务和死锁

实体框架 - SaveChanges 与事务

如何在原始查询中使用实体框架事务?

事务复制活动时实体框架模型的问题