2011-10-07 10 views
5

Sto costruendo un sistema di elaborazione batch. I lotti di Units sono disponibili in quantità da 20 a 1000. Ogni Unit è essenzialmente una gerarchia di modelli (un modello principale e molti modelli figlio). Il mio compito consiste nel salvare ogni gerarchia di un modello in un database come una singola transazione (o si impegna ciascuna gerarchia o si esegue il rollback). Sfortunatamente, EF non è stato in grado di gestire due parti della gerarchia del modello a causa della possibilità di contenere migliaia di record.EF Competing SaveChanges() Chiamate

Quello che ho fatto per risolvere questo problema è impostare SqlBulkCopy per gestire questi due modelli di conteggio potenzialmente elevato e lasciare che EF gestisca il resto degli inserti (e integrità referenziale).

Batch loop:

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

Unità:

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

In questo momento sono sostanzialmente bloccato tra l'incudine e il martello. Se lascio il IsolationLevel impostato su ReadCommitted, ottengo deadlock tra EFINSERT dichiarazioni in diversi Tasks.

Se ho impostato il IsolationLevel-ReadUncommitted (che ho pensato che sarebbe bene visto che non sto facendo alcuna SELECTs) ottengo DbConcurrencyExceptions.

Sono stato in grado di trovare alcun bene informazioni su DbConcurrencyExceptions e Entity Framework ma credo che ReadUncommitted sostanza, causando EF per ricevere "righe inserite" non valide informazioni.

UPDATE

Ecco alcune informazioni su ciò che è effettivamente causando la mia deadlocking problemi mentre si fa INSERTI:

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

A quanto pare questo stesso problema era presente a pochi anni fa, quando LINQ to SQL è uscito e Microsoft lo ha risolto cambiando il modo in cui scope_identity() viene selezionato. Non sono sicuro del motivo per cui la loro posizione è cambiata a questo problema di SQL Server quando lo stesso problema si è verificato con Entity Framework.

+0

_completo_ o _completo_? –

risposta

3

Questo problema viene spiegata abbastanza bene qui: http://connect.microsoft.com/VisualStudio/feedback/details/562148/how-to-avoid-using-scope-identity-based-insert-commands-on-sql-server-2005

Essenzialmente il suo un problema EF interna. Ho migrato il mio codice per utilizzare Linq To SQL e ora funziona correttamente (non è più necessario il valore SELECT non necessario per il valore dell'identità).

citazione rilevante dal medesimo esatto oggetto LINQ to SQL che è stato fissato:

Quando una tabella contiene una colonna di identità, LINQ to SQL genera estremamente inefficiente SQL per l'inserimento in tale tabella. Supponiamo che la tabella sia Order e la colonna identiy sia Id. Il codice SQL generato è:

exec sp_executesql N'INSERT INTO [dbo].[Ordine] ([Colum1], [Colonna2]) VALORI (@ p0, @ p1)

SELECT [t0]. [Id] FROM [dbo]. [Ordine] AS [t0] WHERE [t0]. [Id] = (SCOPE_IDENTITY())', N '@ p0 int, @ p1 int, @ p0 = 124, @ p1 = 432

Come si vede invece di ritornare SCOPE_IDENTITY() direttamente utilizzando ' SELECT SCOPE_IDENTITY() ', l'SQL generato esegue un SELECT sulla colonna Id utilizzando il valore restituito da SCOPE_IDENTITY(). Quando il numero dei record nella tabella è grande, questo rallenta significativamente l'inserimento . Quando la tabella è partizionata, il problema diventa ancora peggio.

Problemi correlati