【发布时间】:2014-04-02 14:07:24
【问题描述】:
在有关实体框架的文档中写道,它不支持开箱即用的悲观并发。这就是为什么在使用 Linq to Entities 语法从表中读取实体时无法锁定表的原因。
假设我们有下一个任务:数据库中有多个线程共享一个表。表具有下一个结构:
Table Counter
Name : varchar[10]
Value : bigint
每个线程都想获取 name = "some name" 的计数器,获取它的计数器值,而不是增加它并保存回数据库。
在这里我们可以看到一个问题,所有这些线程都可以从数据库中读取相同的值并增加它。假设我们有 5 个线程。初始计数器值为1。如果所有线程都读取计数器,则它们都得到值1。然后它们增加它的值并将其保存回数据库。最后我们的计数器值等于 2。但期望值是 6,因为我们希望每个线程都从数据库中获取真正的值。
我认为可以像这样使用具有高度隔离性的事务范围来解决这个问题:
using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions{IsolationLevel = System.Transactions.IsolationLevel.Serializable}))
{
using (var context = new TestDbContext())
{
var counter = context.Counters.First();
Thread.Sleep(2000);
counter.Value ++;
Console.WriteLine("Value={0}", counter.Value);
context.SaveChanges();
}
transactionScope.Complete();
}
没有任何隔离级别允许我们在读取记录后锁定表。在事务 SQL 中,我们有 UPDLOCK 和 HOLDLOCK 关键字,它们允许我们锁定表直到事务完成。 于是我为解决这个问题为 DbContext 创建了一个扩展方法:
public static class DataContextExtensions
{
public static void LockTable<TEntity>(this DbContext context) where TEntity : class
{
var tableName = (context as IObjectContextAdapter).ObjectContext.CreateObjectSet<TEntity>().EntitySet.Name;
List<TEntity> topEntity = context.Database.SqlQuery<TEntity>(String.Format("SELECT TOP 1 * from {0} WITH (UPDLOCK, HOLDLOCK)", tableName)).ToList();
}
}
现在我可以在阅读后想锁定表时使用此方法:
using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions{IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted}))
{
using (var context = new FrontierDbContext())
{
context.LockTable<Counter>();
var counter = context.Counters.First();
Thread.Sleep(2000);
counter.Value ++;
Console.WriteLine("Value={0}", counter.Value);
context.SaveChanges();
}
transactionScope.Complete();
}
之后,所有想要增加计数器值的线程都会从数据库中获取实际值,因为它们会一直等到表空闲。
正如我所说,这是我的解决方法,也许我在某个地方错了。这就是为什么我想请您指出更好的解决方案。因为我不想重新发明轮子 提前致谢!
【问题讨论】:
-
这种场景有什么原因不能使用乐观并发吗?
-
那是我的问题。如何使用 Entity Framework 解决这个问题?
-
实体框架支持乐观并发,您可以将 [Concurrency] 属性应用到您的 Value 属性,这应该会导致您在线程尝试更新计数器的旧值时获得并发异常,
-
我在同样的情况下,我想使用悲观并发,我发现它的独特方法是使用第一个 t-sql 查询来阻止和第二个查询来获取实体,做包括,与他们一起工作,然后保存费用。用EF7好像我们可以用EF使用hints,所以我希望可以避免使用查询来阻塞行。
标签: c# sql multithreading entity-framework