2011-10-07 10 views
5

Estoy construyendo un sistema de procesamiento por lotes. Los lotes de Units vienen en cantidades de 20-1000. Cada Unit es esencialmente una jerarquía de modelos (un modelo principal y muchos modelos secundarios). Mi tarea consiste en guardar cada jerarquía del modelo en una base de datos como una transacción única (ya sea que cada jerarquía se comprometa o se retrotraiga). Desafortunadamente EF no pudo manejar dos porciones de la jerarquía del modelo debido a su potencial para contener miles de registros.FE Competing SaveChanges() Llamadas

Lo que he hecho para resolver esto está configurado en SqlBulkCopy para manejar estos dos modelos de conteo potencialmente alto y dejar que EF maneje el resto de las inserciones (y la integridad referencial).

lotes Loop:

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

Unidad:

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(); 
     } 
    } 
    } 
} 

En este momento estoy esencialmente atrapado entre la espada y la pared. Si dejo el IsolationLevel configurado en ReadCommitted, obtengo interbloqueos entre EFINSERT declaraciones en Tasks diferentes.

Si fijo el IsolationLevel-ReadUncommitted (que pensé que estaría bien ya que no estoy haciendo ninguna SELECTs) consigo DbConcurrencyExceptions.

no he podido encontrar ninguna buena información sobre DbConcurrencyExceptions y Entity Framework pero supongo que ReadUncommitted está esencialmente causando EF de recibir "filas insertadas" no válidos información.

ACTUALIZACIÓN

Aquí hay alguna información básica sobre lo que realmente está causando mi deadlocking problemas mientras se hacen inserciones:

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

Al parecer, este mismo problema estaba presente hace unos años, cuando LINQ to SQL salió y Microsoft lo solucionó cambiando la forma en que se selecciona scope_identity(). No estoy seguro de por qué su posición ha cambiado a ser un problema de SQL Server cuando surgió el mismo problema con Entity Framework.

+0

_competing_ o _completing_? –

Respuesta

3

Este problema se explica bastante bien aquí: http://connect.microsoft.com/VisualStudio/feedback/details/562148/how-to-avoid-using-scope-identity-based-insert-commands-on-sql-server-2005

esencialmente su un problema de EF interna. Migré mi código para usar Linq To SQL y ahora funciona bien (ya no hace el innecesario SELECT para el valor de identidad).

cita relevante desde el mismo problema exacto en LINQ to SQL que se fijó:

Cuando una tabla tiene una columna de identidad, LINQ a SQL genera extremadamente ineficiente SQL para su inserción en una tabla de este tipo. Supongamos que la tabla es Orden y la columna de identificación es Id. El SQL generado es:

exec sp_executesql N'INSERT INTO [dbo].[Orden] ([Colum1], [Columna2]) VALORES (@ p0, @ p1)

SELECCIONE [t0]. [Id] DESDE [dbo]. [Ordenar] AS [t0] DONDE [t0]. [Id] = (SCOPE_IDENTITY())', N '@ p0 int, @ p1 int, @ p0 = 124, @ p1 = 432

Como se puede ver en lugar de devolver SCOPE_IDENTITY() directamente mediante el uso de ' SELECCIONAR SCOPE_IDENTITY() ', el SQL generado realiza una SELECCIÓN en la columna Id utilizando el valor devuelto por SCOPE_IDENTITY(). Cuando el número de los registros en la tabla es grande, esto ralentiza significativamente en la inserción. Cuando la tabla está particionada, el problema empeora .

Cuestiones relacionadas