此问题涉及使用Microsoft Entity Framework处理许多插入或更新的最佳实践。问题是我们编写了一个长期运行的程序,它从数据库中提取数千条记录,然后逐个更新每条记录上的单个字段。令我们沮丧的是,我们意识到更新的每个记录都会在未处理ObjectContext的时间内被锁定。下面是一些伪代码(实际上没有运行)来说明:
using(ObjectContext context = new ObjectContext())
{
var myRecords = context.CreateObjectSet<MyType>().AsQueryable();
foreach(var record in myRecords)
{
record.MyField = "updated!";
context.SaveChanges();
//--do something really slow like call an external web service
}
}
问题是我们需要做很多更新而不考虑交易。我们惊讶地发现调用context.SaveChanges()实际上会在记录上创建锁,并且在释放ObjectContext之前不会释放它。我们特别不想锁定数据库中的记录,因为这是一个高流量系统,程序可能会运行数小时。
所以问题是:在Microsoft Entity Framework 4中进行许多更新的最佳方法是什么,而不是在锁定数据库的一个长事务上完成所有更新?我们希望答案不是为每个更新创建一个新的ObjectContext ......
答案 0 :(得分:7)
默认情况下,SQL服务器上的实体框架使用读取已提交事务隔离级别,并在SaveChanges
结束时提交事务。如果您怀疑其他行为,则必须使用其余代码(您使用的是TransactionScope
吗? - 您没有在代码中显示它)或者它必定是一些错误。
你的方法也错了。如果要单独保存每条记录,还应单独加载每条记录。 EF对于这类应用来说绝对是不错的选择。即使您只使用单个SaveChange
来更新所有记录,它仍然会为每次更新单个往返数据库。
答案 1 :(得分:4)
这些锁不是由Entity Framework创建的。 EF仅支持乐观并发,EF不支持悲观锁定。
我认为您遇到的锁定是SQL Server配置的结果。也许如果服务器上的事务隔离级别设置为REPEATABLE READ,则可能会在每次查询后导致锁定。但我不确定哪个配置设置可能正好是问题。更多详细信息为here。
修改强>
关于EF中的事务和事务隔离的另一篇有用的文章是here。强烈建议始终明确设置隔离级别。引用文章:
如果你不控制[ 隔离级别],你不知道 哪个事务隔离级别你的 查询将正在运行。毕竟, 你不知道连接在哪里 你从游泳池得到了 [...]你只是继承了最后使用的 连接上的隔离级别,所以 你不知道哪种类型的锁 你的被采取(或更糟:被忽略) 查询以及这些锁的持续时间 将举行。在繁忙的数据库中,这个 肯定会导致随机错误, 超时和死锁。
答案 2 :(得分:2)
我可能错了,但我相信您不应该每次都调用SaveChanges(),因为此时将更改应用于数据库。相反,在对象更改结束时应用SaveChanges(),或使用计数器不那么频繁地执行。
答案 3 :(得分:1)
在我们的应用程序中,我们有类似的场景,尽可能避免锁定运行大量选择,然后在内存操作中创建大量插入。
解决方案A )使用包含读取和更新的事务范围 PRO:数据安全更新 缺点:由读取(可重复读取)和更新
引起的锁定解决方案B )不要使用事务并一起更新所有数据 PRO:数据安全更新,但您读取的数据可能已同时更改 缺点:整个持续时间更新导致的锁定(默认情况下EF创建一个事务)
解决方案C )批量更新而不是一起更新所有数据(仅当select不锁定表时才可用,否则您获得与B相同的行为 PRO:更新的表中更短和更小的锁 缺点:您增加了受数据过时影响的变化
解决方案D )分解问题并拆分读取可以帮助您减少锁定,这样您就可以使用事务范围来包装读取和写入(作为sol.A) PRO:数据安全更新 缺点:读取(可重复读取)和更新导致的锁定,影响因批量大小和查询本身的性质而异
解决方案E )不要使用事务,因此只有更新才会产生小锁(如sol.B) PRO:数据安全更新,但您读取的数据可能已同时更改 缺点:由更新引起的锁定
正如@Ladislav正确指出的那样,多个插入实际上效率很低,并且数据库上的快速分析会向您显示ORM魔法在这种情况下是如何失败的。 如果您想使用EF执行批量操作,例如插入,更新和删除,我建议您看一下: EF Utilities
我倾向于使用此查询测试锁,我希望可能有助于更好地了解正在发生的事情。
SELECT
OBJECT_NAME(p.OBJECT_ID) AS TableName,
resource_type,
resource_description
FROM
sys.dm_tran_locks l JOIN
sys.partitions p ON
l.resource_associated_entity_id = p.hobt_id