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