2013-10-28 11 views
6

Devo elaborare come entità 1M per creare fatti. Ci dovrebbe essere circa la stessa quantità di fatti risultanti (1 milione).La memoria cresce in modo imprevisto mentre si utilizza l'entity framework per l'inserimento bulk

Il primo problema che ho avuto è stato l'inserimento di massa che era lento con il framework di entità. Quindi ho usato questo modello Fastest Way of Inserting in Entity Framework (risposta da SLauma). E posso inserire entità molto velocemente ora a 100K in un minuto.

Un altro problema che ho riscontrato è la mancanza di memoria per elaborare tutto. Quindi ho "impaginato" l'elaborazione. Per evitare un'eccezione di memoria, mi piacerebbe ottenere una lista dei miei 1 milioni di risultati.

Il problema che ho è che la memoria cresce sempre anche con il paging e non capisco perché. Dopo ogni lotto non viene rilasciata memoria. Penso che sia strano perché prendo i riconi per costruire fatti e archiviarli nel DB ad ogni iterazione del ciclo. Non appena il ciclo è completato, questi dovrebbero essere rilasciati dalla memoria. Ma sembra non perché nessuna memoria viene rilasciata dopo ogni iterazione.

Puoi dirmi se vedi qualcosa di sbagliato prima di scavare di più? Più precisamente, perché non viene rilasciata alcuna memoria dopo un'iterazione del ciclo while.

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

Il metodo fornito da Slauma per ottimizzare l'inserimento di massa con struttura entità.

class ContextHelper 
{ 
    public static ReceiptsItemCodeAnalysisContext AddToContext(ReceiptsItemCodeAnalysisContext context, 
    ReconFact entity, int count, int commitCount, bool recreateContext) 
    { 
    context.Set<ReconFact>().Add(entity); 

    if (count % commitCount == 0) 
    { 
     context.SaveChanges(); 
     if (recreateContext) 
     { 
     context.Dispose(); 
     context = new ReceiptsItemCodeAnalysisContext(); 
     context.Configuration.AutoDetectChangesEnabled = false; 
     } 
    } 
    return context; 
    } 
} 
+1

BTW, state molto attenti con istruzioni come 'thisReconFacts.ForEach (f => facts.Add (f));' in un contesto 'Parallel.ForEach'. 'Lista .Add (T)' non è thread-safe. –

+0

Sì, lo so che dovrei usare le raccolte thread-safe. Lo farò come ulteriori miglioramenti. Per prima cosa ho dovuto capire un modo veloce per inserire tonnellate di dati con EF. –

+1

Chi ha detto che dovresti usare le raccolte thread-safe? Cosa dire 'Elenco fatti = (da ricondurre aProcess.AsParallel() da fact in ReconFactGenerator.Generate (recon) select fact) .ToList();'? –

risposta

3

provare a dire del contesto oggetto non per tenere traccia degli oggetti, in questo modo:

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

//... 

Nel codice che avete, tutti gli oggetti di un milione di Recon si accumulano in memoria fino a quando il contesto dell'oggetto è disposto.

+0

Questo sembra fare il trucco! Molte grazie ! –

1

Poiché si dispone dello stesso contesto di dati durante la corsa, è presumibilmente il caching. In generale, quando ho affrontato questo problema, ho trovato più semplice garantire che ogni "batch" abbia il proprio datacontext che va fuori dal campo di applicazione per iterazione.

+0

Ho provato a creare un contesto di dati ad ogni iterazione ma non cambia nulla ... –

+0

Anche il 'recon' db nella parte superiore del tuo metodo? –

+0

Sì. Ho provato a inserire tutte le istanze di contesto in una {} istruzione, ma purtroppo non è stata apportata alcuna modifica. –