与实体框架的悲观并发

Boo*_*ius 5 c# sql multithreading entity-framework

在关于实体框架的文档中写道,它不支持开箱即用的悲观并发.这就是为什么在使用Linq to Entities语法从中读取实体时无法锁定表的原因.

让我们假设我们有下一个任务:有多个线程共享数据库中的一个表.表有下一个结构:

Table Counter
Name : varchar[10]
Value : bigint
Run Code Online (Sandbox Code Playgroud)

每个线程都希望得到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();
    }
Run Code Online (Sandbox Code Playgroud)

非隔离级别允许我们在从中读取记录后锁定表.在transact 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我可以使用这个方法,当我想在读取之后锁定表格如下:

    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();
    }
Run Code Online (Sandbox Code Playgroud)

之后,所有想要增加计数器值的线程都将从数据库中获取实际值,因为它们将一直等到表空闲.

正如我所说,这是我的解决方法,也许我错了.这就是为什么我想请你指出更好的解决方案.因为我不想重新发明轮子提前谢谢!

小智 0

在您的情况下,您可以在 TransactionScope using 块中针对这种特定情况使用解决方法,如下所示:

const string updateStr = "update table set counter = counter + 1";
context.ExecuteSqlCommand(updateStr);
context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)