2011-10-07 2 views
5

Je construis un système de traitement par lots. Les lots de Units viennent en quantités de 20-1000. Chaque Unit est essentiellement une hiérarchie de modèles (un modèle principal et de nombreux modèles enfants). Ma tâche consiste à enregistrer chaque hiérarchie de modèle dans une base de données en tant que transaction unique (chaque hiérarchie valide ou annule). Malheureusement, EF n'a pas pu gérer deux parties de la hiérarchie du modèle en raison de leur potentiel de contenir des milliers d'enregistrements. Ce que j'ai fait pour résoudre ceci est mis en place SqlBulkCopy pour gérer ces deux modèles potentiellement élevés et laissez EF gérer le reste des insertions (et l'intégrité référentielle).EF Concurrence SaveChanges() Appels

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

En ce moment je suis essentiellement coincé entre le marteau et l'enclume. Si je laisse le IsolationLevel mis à , je reçois des interblocages entre EFINSERT déclarations dans différents Tasks.

Si je mets le IsolationLevel-ReadUncommitted (que je pensais que ce serait bien que je ne fais pas SELECTs) Je reçois DbConcurrencyExceptions.

Je suis incapable de trouver de bonnes informations sur DbConcurrencyExceptions et Entity Framework mais je devine que ReadUncommitted est essentiellement à l'origine EF pour recevoir invalides « lignes insérées » informations.

MISE À JOUR

Voici quelques informations de base sur ce qui est réellement la cause de mes problèmes interblocage tout en faisant INSERTS:

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

Apparemment, ce même problème était présent il y a quelques années quand LINQ to SQL est sorti et Microsoft l'a corrigé en changeant la méthode de sélection de scope_identity(). Vous ne savez pas pourquoi leur position a changé en étant un problème SQL Server lorsque le même problème est apparu avec Entity Framework.

+0

_competing_ ou _completing_? –

Répondre

3

Cette question est assez bien expliqué ici: http://connect.microsoft.com/VisualStudio/feedback/details/562148/how-to-avoid-using-scope-identity-based-insert-commands-on-sql-server-2005

Essentiellement sa question EF interne. J'ai migré mon code pour utiliser Linq To SQL et il fonctionne maintenant bien (ne fait plus l'inutile SELECT pour la valeur d'identité).

citation pertinente de l'exacte même problème dans LINQ to SQL qui a été fixé:

Lorsqu'une table a une colonne d'identité, LINQ to SQL génère extrêmement SQL inefficace pour l'insertion dans une telle table. Supposons que la table est Order et que la colonne identiy est Id. Le code SQL généré est:

exec sp_executesql N'INSERT INTO [dbo].[Ordre] ([Colum1], [Colonne2]) VALEURS (@ p0, @ p1)

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

Comme on peut le voir au lieu de retourner SCOPE_IDENTITY() directement en utilisant ' SELECT SCOPE_IDENTITY() ', le SQL généré effectue un SELECT sur la colonne Id en utilisant la valeur renvoyée par SCOPE_IDENTITY(). Lorsque le nombre des enregistrements dans le tableau est grand, cela ralentit significativement vers le bas de l'insertion. Lorsque la table est partitionnée, le problème devient encore pire.

Questions connexes