EF Competing SaveChanges() Calls

本文关键字:Calls SaveChanges Competing EF | 更新日期: 2023-09-27 18:10:37

我正在构建一个批处理系统。Units的批次数量为20-1000。每个Unit本质上是一个模型层次结构(一个主模型和许多子模型)。我的任务包括将每个模型层次结构作为单个事务保存到数据库中(每个层次结构提交或回滚)。不幸的是,EF无法处理模型层次结构的两个部分,因为它们可能包含数千条记录。

为了解决这个问题,我设置了SqlBulkCopy来处理这两个潜在的高计数模型,并让EF处理其余的插入(和引用完整性)。

批循环:

foreach (var unitDetails in BatchUnits)
{
  var unitOfWork = new Unit(unitDetails);
  Task.Factory.StartNew(() =>
    {
      unitOfWork.ProcessX(); // data preparation
      unitOfWork.ProcessY(); // data preparation
      unitOfWork.PersistCase();
    });
}
单位:

class Unit
{
  public PersistCase()
  {
    using (var dbContext = new CustomDbContext())
    {
      // Need an explicit transaction so that 
      // EF + SqlBulkCopy act as a single block
      using (var scope = new TransactionScope(TransactionScopeOption.Required,
        new TransactionOptions() {
          IsolationLevel = System.Transaction.IsolationLevel.ReadCommitted
        }))
      {
        // Let EF Insert most of the records
        // Note Insert is all it is doing, no update or delete
        dbContext.Units.Add(thisUnit);
        dbContext.SaveChanges();  // deadlocks, DbConcurrencyExceptions here
        // Copy Auto Inc Generated Id (set by EF) to DataTables
        // for referential integrity of SqlBulkCopy inserts
        CopyGeneratedId(thisUnit.AutoIncrementedId, dataTables);
        // Execute SqlBulkCopy for potentially numerous model #1
        SqlBulkCopy bulkCopy1 = new SqlBulkCopy(...);
        ...
        bulkCopy1.WriteToServer(dataTables["#1"]);
        // Execute SqlBulkCopy for potentially number model #2
        SqlBulkCopy bulkCopy2 = new SqlBulkCopy(...);
        ...
        bulkCopy2.WriteToServer(dataTables["#2"]);
        // Commit transaction
        scope.Complete();
      }
    }
  }
}

现在我基本上是被困在岩石和一个艰难的地方之间。如果我将IsolationLevel设置为ReadCommitted,我将在不同Tasks中的EF INSERT语句之间获得死锁。

如果我将IsolationLevel设置为ReadUncommitted(我认为这很好,因为我没有做任何SELECTs),我得到DbConcurrencyExceptions

我一直无法找到任何关于DbConcurrencyExceptionsEntity Framework的好信息,但我猜ReadUncommitted基本上是导致EF接收无效的"插入行"信息。

下面是关于在执行insert时实际导致死锁问题的一些背景信息:

http://connect.microsoft.com/visualstudio/feedback/details/562148/how - -避免使用范围- - -插入-命令基于身份的sql - server - 2005

很明显,同样的问题在几年前Linq To SQL出现时就出现了,微软通过改变scope_identity()的选择方式来修复它。不知道为什么他们的立场已经改变为这是一个SQL Server问题,当同样的问题出现了实体框架

EF Competing SaveChanges() Calls

这个问题在这里解释得很好:http://connect.microsoft.com/VisualStudio/feedback/details/562148/how-to-avoid-using-scope-identity-based-insert-commands-on-sql-server-2005

本质上是EF的内部问题。我将我的代码迁移到使用Linq to SQL,它现在工作得很好(不再为标识值做不必要的SELECT)。

从Linq To Sql中完全相同的问题中引用的相关引用已修复:

当一个表有一个标识列时,Linq to SQL会生成一个极端将SQL插入到这样的表中效率低下。假设表格是顺序和标识列是Id。生成的SQL为:

exec sp_executesql N'INSERT INTO [dbo]。(顺序)([Colum1], [Column2])VALUES (@p0, @p1)

选择(t0)。[Id] FROM [dbo].][Order] AS [t0] WHERE [t0]。(Id) =(SCOPE_IDENTITY ())',N'@p0 int,@p1 int,@p0=124,@p1=432

可以看到,不是直接返回SCOPE_IDENTITY(),而是使用'SELECT SCOPE_IDENTITY()',生成的SQL对对象执行SELECT操作使用SCOPE_IDENTITY()返回的值返回Id列。当表中的记录数量很大,这将显著降低速度向下插入。当表被分区时,问题变得更糟。