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
。
我一直无法找到任何关于DbConcurrencyExceptions
和Entity Framework
的好信息,但我猜ReadUncommitted
基本上是导致EF
接收无效的"插入行"信息。
下面是关于在执行insert时实际导致死锁问题的一些背景信息:
http://connect.microsoft.com/visualstudio/feedback/details/562148/how - -避免使用范围- - -插入-命令基于身份的sql - server - 2005
很明显,同样的问题在几年前Linq To SQL出现时就出现了,微软通过改变scope_identity()的选择方式来修复它。不知道为什么他们的立场已经改变为这是一个SQL Server问题,当同样的问题出现了实体框架
这个问题在这里解释得很好: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列。当表中的记录数量很大,这将显著降低速度向下插入。当表被分区时,问题变得更糟。