EF Competing SaveChanges () Llamadas

.net c# entity-framework sqlbulkcopy transactions

Pregunta

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 de modelos en una base de datos como una sola transacción (cada jerarquía confirma o retrocede). Desafortunadamente, EF no pudo manejar dos partes de la jerarquía del modelo debido a su potencial para contener miles de registros.

Lo que he hecho para resolver esto es configurar SqlBulkCopy para manejar estos dos modelos de conteo potencialmente alto y permitir que EF maneje el resto de las inserciones (y la integridad referencial).

Bucle de lote

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 una roca y un lugar duro. Si dejo el valor de IsolationLevel establecido en ReadCommitted , obtengo puntos muertos entre EF INSERT EF INSERT en diferentes Tasks .

Si configuro IsolationLevel en ReadUncommitted (lo cual pensé que estaría bien ya que no estoy haciendo ningún SELECTs ) obtengo DbConcurrencyExceptions .

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

ACTUALIZAR

Aquí hay información de antecedentes sobre lo que realmente está causando mis problemas de interbloqueo al hacer INSERTOS:

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

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

Respuesta aceptada

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 es un problema interno de EF. Migré mi código para usar Linq To SQL y ahora funciona bien (ya no hace el SELECT innecesario para el valor de identidad).

Cita relevante del mismo problema exacto en Linq To Sql que se solucionó:

Cuando una tabla tiene una columna de identidad, Linq to SQL genera un SQL extremadamente ineficiente para su inserción en dicha tabla. Suponga que la tabla es Orden y la columna de identidad es Id. El SQL generado es:

exec sp_executesql N'INSERT INTO [dbo]. [Order] ([Colum1], [Column2]) VALUES (@ p0, @ p1)

SELECCIONE [t0]. [Id] DE [dbo]. [Orden] 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 usando 'SELECT SCOPE_IDENTITY ()', el SQL generado realiza un SELECT en la columna de Id. Usando el valor devuelto por SCOPE_IDENTITY (). Cuando el número de registros en la tabla es grande, esto ralentiza significativamente la inserción. Cuando la tabla está particionada, el problema empeora aún más.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué