2013-10-28 6 views
6

Ich muss wie 1M Entitäten verarbeiten, um Fakten zu erstellen. Es sollte etwa die gleiche Menge an resultierenden Fakten geben (1 Million).Speicher wächst unerwartet während Entity Framework zu Massen einfügen

Das erste Problem, das ich hatte, war die Masseneinfügung es war langsam mit Entity Framework. Also habe ich dieses Muster verwendet Fastest Way of Inserting in Entity Framework (Antwort von SLauma). Und ich kann in einer Minute jetzt wirklich schnell 100K einfügen.

Ein anderes Problem, das ich fand, ist der Mangel an Speicher, um alles zu verarbeiten. Also habe ich die Verarbeitung "ausgelagert". Um eine Ausnahmebedingung zu vermeiden, würde ich bekommen, wenn ich eine Liste meiner 1 Million resultierenden Fakten mache.

Das Problem, das ich habe, ist, dass die Erinnerung immer mit dem Paging wächst und ich verstehe nicht warum. Nach jedem Batch wird kein Speicher freigegeben. Ich denke, das ist komisch, weil ich Fakten rekonstruieren und sie bei jeder Iteration der Schleife in der Datenbank ablege. Sobald die Schleife abgeschlossen ist, sollten diese aus dem Speicher freigegeben werden. Aber es sieht nicht so aus, weil nach jeder Iteration kein Speicher freigegeben wird.

Können Sie mir bitte sagen, wenn Sie etwas falsch sehen, bevor ich mehr graben? Genauer gesagt, warum nach einer Iteration der while-Schleife kein Speicher freigegeben wird.

static void Main(string[] args) 
{ 
    ReceiptsItemCodeAnalysisContext db = new ReceiptsItemCodeAnalysisContext(); 

    var recon = db.Recons 
    .Where(r => r.Transacs.Where(t => t.ItemCodeDetails.Count > 0).Count() > 0) 
    .OrderBy(r => r.ReconNum); 

    // used for "paging" the processing 
    var processed = 0; 
    var total = recon.Count(); 
    var batchSize = 1000; //100000; 
    var batch = 1; 
    var skip = 0; 
    var doBatch = true; 

    while (doBatch) 
    { // list to store facts processed during the batch 
    List<ReconFact> facts = new List<ReconFact>(); 
    // get the Recon items to process in this batch put them in a list 
    List<Recon> toProcess = recon.Skip(skip).Take(batchSize) 
     .Include(r => r.Transacs.Select(t => t.ItemCodeDetails)) 
     .ToList(); 
    // to process real fast 
    Parallel.ForEach(toProcess, r => 
    { // processing a recon and adding the facts to the list 
     var thisReconFacts = ReconFactGenerator.Generate(r); 
     thisReconFacts.ForEach(f => facts.Add(f)); 
     Console.WriteLine(processed += 1); 
    }); 
    // saving the facts using pattern provided by Slauma 
    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new System.TimeSpan(0, 15, 0))) 
    { 
     ReceiptsItemCodeAnalysisContext context = null; 
     try 
     { 
     context = new ReceiptsItemCodeAnalysisContext(); 
     context.Configuration.AutoDetectChangesEnabled = false; 
     int count = 0; 

     foreach (var fact in facts.Where(f => f != null)) 
     { 
      count++; 
      Console.WriteLine(count); 
      context = ContextHelper.AddToContext(context, fact, count, 250, true); //context.AddToContext(context, fact, count, 250, true); 
     } 
     context.SaveChanges(); 
     } 
     finally 
     { 
     if (context != null) 
      context.Dispose(); 
     } 
     scope.Complete(); 
    } 
    Console.WriteLine("batch {0} finished continuing", batch); 
    // continuing the batch 
    batch++; 
    skip = batchSize * (batch - 1); 
    doBatch = skip < total; 
    // AFTER THIS facts AND toProcess SHOULD BE RESET 
    // BUT IT LOOKS LIKE THEY ARE NOT OR AT LEAST SOMETHING 
    // IS GROWING IN MEMORY 
    } 
    Console.WriteLine("Processing is done {} recons processed", processed); 
} 

Die von Slauma zur Verfügung gestellte Methode zur Optimierung der Masseneinfügung mit Entity-Framework.

+1

BTW, seien Sie sehr vorsichtig mit Anweisungen wie 'thisReconFacts.ForEach (f => facts.Add (f));' in einem 'Parallel.ForEach' Kontexten. 'Liste .Add (T)' ist nicht threadsicher. –

+0

Ja, ich weiß, dass ich Thread-sichere Sammlungen verwenden sollte. Ich werde das als weitere Verbesserungen tun. Zuerst musste ich einen schnellen Weg finden, Tonnen von Daten mit EF einzufügen. –

+1

Wer sagt, dass Sie threadsichere Sammlungen verwenden sollten? Was ist mit 'Liste facts = (aus Recon in toProcess.AsParallel() von Tatsache in ReconFactGenerator.Generate (Recon) wählen Sie die Tatsache) .ToList();'? –

Antwort

3

versuchen, das Objekt Kontext zu sagen, nicht die Objekte zu verfolgen, wie folgt aus:

static void Main(string[] args) 
{ 
    ReceiptsItemCodeAnalysisContext db = new ReceiptsItemCodeAnalysisContext(); 

    var recon = db.Recons 
     .AsNoTracking() // <---- add this 
     .Where(r => r.Transacs.Where(t => t.ItemCodeDetails.Count > 0).Count() > 0) 
     .OrderBy(r => r.ReconNum); 

//... 

Im Code, den Sie haben alle eine Million Recon Objekte im Speicher ansammeln, bis der Objektkontext angeordnet ist.

+0

Das scheint der Trick zu sein! Danke vielmals ! –

1

Da Sie während des gesamten Laufs denselben Datenkontext haben, wird vermutlich zwischengespeichert. Allgemein gesagt, als ich mit diesem Problem konfrontiert wurde, fand ich es am einfachsten, sicherzustellen, dass jeder "Stapel" seinen eigenen Datenkontext hat, der pro Iteration den Geltungsbereich verlässt.

+0

Ich habe versucht, einen Datenkontext bei jeder Iteration zu erstellen aber es ändert nichts ... –

+0

Sogar die 'recon' db an der Spitze Ihrer Methode? –

+0

Ja, ich habe versucht, jede Kontextinstanz in eine Anweisung {} zu setzen, aber leider keine Änderung. –