EF Competing SaveChanges()调用

时间:2011-10-07 14:22:21

标签: c# .net entity-framework transactions sqlbulkcopy

我正在构建一个批处理系统。批量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,我会在不同EF的{​​{1}} INSERT语句之间出现死锁。

如果我将Tasks设置为IsolationLevel(由于我没有做ReadUncommitted,我认为这样会很好),我得到SELECTs

我一直无法找到有关DbConcurrencyExceptionsDbConcurrencyExceptions的任何有用信息,但我猜测Entity Framework实际上导致ReadUncommitted收到无效的“插入行” “信息。

更新

以下是有关在执行INSERTS时实际导致死锁问题的一些背景信息:

http://connect.microsoft.com/VisualStudio/feedback/details/562148/how-to-avoid-using-scope-identity-based-insert-commands-on-sql-server-2005

显然,几年前Linq To SQL问世以及Microsoft通过更改scope_identity()的选择方式来解决这个问题。当Entity Framework出现同样的问题时,不确定为什么他们的位置已经改变为SQL Server问题。

1 个答案:

答案 0 :(得分:3)

这个问题在这里得到了很好的解释: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]。[Order]([Colum1],[Column2])   价值观(@ p0,@ p1)

     

SELECT [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   Id列使用SCOPE_IDENTITY()返回的值。当。。。的时候   表中记录的数量很大,这显着减慢   插入。当表被分区时,问题就出现了   更糟糕的是。