使用事务检测Entity Framework中的并发问题

时间:2014-10-08 19:29:55

标签: c# sql-server entity-framework concurrency

我保存了用于帮助最小化并发问题的代码。它的结构类似于:

// HasConcurrencyIssues opens a new EntityContext and gets the actual current
// details and then compares it to what was loaded for the form
if (this.HasConcurrencyIssues()) {

    // can't save, exit out of save code
    return;
}

using (EntityContext context = new EntityContext()) {

    // shared locks to avoid dirty reads - should I be using this?       
    IsolationLevel level = IsolationLevel.ReadCommitted;

    using (DbContextTransaction trans == context.Database.BeginTransaction(level)) {

        try {

            // here is where I save stuff using the context by adding
            // items to the DbSets
            this.SaveStuff(context);

            // execute the generated SQL commands before secondary check because
            // this could take some time
            context.SaveChanges();

            // check again in case something happened after executing SaveChanges()
            // ** gets blocked here **
            if (this.HasConcurrencyIssues()) {

                // can't save, rollback and exit out of save code
                trans.Rollback();
                return;
            }

            trans.Commit();
        } catch (Exception ex) {

            // handle error
            trans.Rollback();
        }
    }
}

我使用此结构来帮助最小化并发问题。我遇到的问题是我并不真正理解ReadCommitted和并发如何协同工作。我注意到在第二次检查并发问题时它被阻​​止了,因为它无法读取当前记录(我假设这与我选择的IsolationLevel有关)。我担心如果我删除那个二次检查,它可能会导致我不想要的幻像数据。

应用程序正在扩展,需要能够处理大约200个可能想要编辑同一个对象并保存的用户。我基本上想要一个硬停止来检查对象是否已经改变,如果有,则阻止保存。

如何构建我的保存代码以最小化并发问题?

修改

我意识到它被锁定了,因为支票是在SaveChanges电话之后。如果我在SaveChanges调用之前进行二次检查它会起作用,但是在保存时事务是否可能完成,这会导致并发问题出现,但不会?


相关系统架构:

  • Entity Framework 6
  • SQL Server 2012

3 个答案:

答案 0 :(得分:0)

我认为这是一种更简单的方法:

using (var ts = new TransactionScope())
{
    if (this.HasConcurrencyIssues()) {
        // can't save, exit out of save code
        return;
    }

    using (EntityContext context = new EntityContext()) {
        this.SaveStuff(context);
        context.SaveChanges();
    }

    ts.Complete();
}

因为当您从数据库中读取信息时,事务范围已经开始,所以在您完成保存之前,不允许其他任何线程更改该信息。

同样,如果另一个线程已经读取信息以确定是否存在任何并发问题,则对HasConcurrencyIssues()的调用将不会返回,直到它们完成保存更改为止。

如果事务范围尚未完成,则事务范围将在using块的末尾自动回滚,因此不需要额外的try / catch逻辑。

答案 1 :(得分:0)

对同一数据处理两个事务是一件麻烦事。您将获得各种阻止行为,通常取决于查询计划。在测试期间,这可能会起作用,然后在负载下中断您将创建分布式死锁(涉及多个连接)。 SQL Server无法破坏它们。您基本上会锁定一些数据并在30秒超时期间暂停执行。可伸缩性杀手。

使用单个上下文,连接和事务。锁定正确。

要么更好地理解SQL Server中的锁定并应用正确的隔离级别,也许应该使用悲观的锁定提示。或者,使用始终有效的解决方案:SERIALIZABLE。如果只有一个事务同时运行,则此隔离级别可以保证结果。通过重试事务来处理死锁。

SERIALIZABLE往往会导致更多阻塞和死锁。根据工作量的不同,这可能根本不是问题,也可能是致命的。

为读者调查SNAPSHOT隔离。它消除了对数据的所有锁定,从而实现了完美的数据一致性。

这实际上是我可以给出的所有建议,而没有关于具体查询和锁定的信息。

答案 2 :(得分:0)

除非您不通过SET TRANSACTION ISOLATION LEVEL命令或应用程序中的任何其他位置在sql server中更改隔离级别,否则不必将隔离级别指定为ReadCommitted。因为ReadCommitted是sql server中的默认设置。

审核herehere以了解可能的情况会很有用。