2016-05-31 26 views
1

Ich habe eine Konsolenanwendung, die über LINQ etwa 150.000 Zeilen in die Datenbank speichert. Das funktioniert gut (wo ich normalerweise erwarten würde, dass es nicht mehr funktioniert). Es ist ein Moor Standard Änderungen speichern, nachdem Daten aus der CSV-Datei zu lesen nennen: -LINQ SaveChanges() außergewöhnlich langsam

List<Invoice> oldInvoices = db.Invoices.Where(x => !x.IsVisible).ToList(); 
List<int> oldInvoiceIDs = oldInvoices.Select(s => s.InvoiceID).ToList(); 

List<InvoiceProduct> allInvoiceProducts = db.InvoiceProducts.ToList(); 
List<InvoiceProduct> oldInvoiceProducts = allInvoiceProducts.Where(x => oldInvoiceIDs.Contains(x.InvoiceID)).ToList(); 

db.InvoiceProducts.RemoveRange(oldInvoiceProducts); 
db.Invoices.RemoveRange(oldInvoices); 

UpdateConsole.WriteLine("Switching over invoices completed. Please wait...", ConsoleColor.Black, ConsoleColor.Magenta); 

Die Tabelle eine Liste von Rechnungen mit einer Unter verknüpften Tabelle von Produkten gegen jede Rechnung ist. Jedes Mal, wenn wir neue Daten erhalten, schreiben wir die neuen Daten ein, markieren sie in der Datenbank als nicht sichtbar, schalten dann die derzeit sichtbaren Daten in unsichtbare und die derzeit unsichtbaren Daten in sichtbare um, was den sofortigen Wechsel von einem Datensatz zu der nächste Datensatz Der gerade als unsichtbar markierte Datensatz wird dann über LINQ gelöscht.

Dies dauert seine Zeit zu löschen, aber nicht eine unangemessene Menge an Zeit. Da diese Daten aus einer CSV-Datei stammen, protokollieren wir die Anzahl der Zeilen und starten und beenden Datum und Uhrzeit des Lesens der Datei. Dies wird in einer anderen Datenbank-Tabelle gespeichert und den Code zu speichern ist: -

importLog.SuccessfullyImportedRows = successfulRows; 
importLog.FailedImportedRows = failedRows; 
importLog.EndTime = DateTime.Now; 

db.SaveChanges(); 

Dieses retten, nimmt über 40 Minuten und ich habe keine Ahnung, warum. Das einzige, was mir einfällt, ist, dass es dieselbe DBEntities-Klasse verwendet, die verfügbar gemacht wird, wenn das EDMX in Visual Studio generiert wird?

Hat jemand das gehabt? Es gibt das Aussehen der Anwendung hängen, aber es nach 40 Minuten nicht weiter oder so ...

+0

Mögliches Duplikat von [Wie kann ich diese Aufgabe schneller ausführen] (http://stackoverflow.com/questions/37374480/how-can-i-run-this-task-faster) – Veverke

+0

Kann Ihnen helfen, http: // stackoverflow.com/questions/37096509/why-getting-data-with-entity-framwork-is-slow – mohsen

+0

Danke für die Links dort. Letzteres bezieht sich auf SaveChanges() innerhalb einer Schleife, was meine nicht ist. Es ist auch erwähnenswert, dass wenn ich die 177.000 Datensätze speichern, ich in einer lokalen Liste speichern, und dann AddRange() und dann SaveChanges(). Ich rufe die SaveChanges() nur einmal zum Speichern der Liste und ein zweites Mal zum Speichern des Protokolls auf. Kann das selbe Problem auch ohne geloopte SaveChanges() bestehen? –

Antwort

1

Sie mehrere Performance-Probleme haben in Ihrem Ansatz:

  1. Ziehen Sie nicht benötigten Daten aus der Datenbank.
  2. Bulk-Insert für riesige Datensätze.
  3. Massenlöschung von Hude Records.

Keine Notwendigkeit, alle Rechnungen aus der Datenbank zieht sie dann lokal im Speicher filtern, wo Sie sie direkt in der Datenbank und retreive nur die Liste, die Sie abfragen können.

Sie müssen diese ersetzt werden:

List<InvoiceProduct> allInvoiceProducts = db.InvoiceProducts.ToList(); 
List<InvoiceProduct> oldInvoiceProducts = allInvoiceProducts.Where(x => oldInvoiceIDs.Contains(x.InvoiceID)).ToList(); 

mit:

List<InvoiceProduct> oldInvoiceProducts = db.InvoiceProducts.Where(x => oldInvoiceIDs.Contains(x.InvoiceID)).ToList(); 

für Groß schnellen Ansatz Löschen:

String commaDelimitedIds = String.Join(",", oldInvoiceIDs); 
String query = "DELETE FROM Invoice WHERE InvoiceID IN (" + commaDelimitedIds + ")"; 
db.ExecuteQuery(query); 

über Linq To SQL150,000 recod Einsetzen keine gute Idee ist, Dies erzeugt 150,000Insert Anweisung (Nein, um Beziehungsobjekte zu erwähnen).

Werfen Sie einen Blick auf dieses Beispiel: SQLBulkCopy, das ideal für große Einsätze ist.

Im Allgemeinen sind ORMs keine gute Idee für Bulk-Operationen.

+0

Hallo und danke für deine Antwort. Sie haben richtig gesagt, dass ich unnötige Daten aus der Datenbank gezogen habe. Das Problem, das ich vorher hatte, war, dass die dritte Codezeile nach "mit:" einen Fehler zurückgab, weil .Contains() zu viele Ganzzahlen in der Liste hatte. Daher habe ich eine lokale Liste in einer zusätzlichen Zeile ausgewählt und dann oben meinen Ansatz gemacht. Vielen Dank für Ihre Hilfe! Ich werde es versuchen. –

+1

@MikeUpjohn Sie können jede 2000 ID zusammen mit einer for-Schleife aufteilen, und 'Concat' die Listen am Ende zusammen. Dies ist eine Umgehung für die Einschränkung der SQL-Parameter. – user3185569

1

Zunächst einmal, in Ihrer Abfrage sehe ich, dass Probleme bei der Verwendung .toList() hat. toList bedeutet, dass Sie diese Abfrage sofort ausführen und im Speicher ablegen.Es ist schneller für kleine Daten, aber für mehr als 150.000 Zeilen, sicherlich werden Sie das Problem mit der Leistung und aus dem Speicher damit bekommen. Sie können AsQueryable() anstelle von verwenden.

AsQueryable erstellt nur eine Abfrage, die Anweisungen benötigt, um eine Liste zu erhalten. Sie können später weitere Änderungen an der Abfrage vornehmen, z. B. indem Sie neue Where-Klauseln hinzufügen, die bis zur Ebene der Datenbank gesendet werden.

Für EF 6 oder obere hat RemoveRange sehr schnelle Leistung. Also ich glaube nicht RemoveRange ist die Ursache in hier. Wenn Sie jedoch mehr Leistung erzielen möchten, versuchen Sie, diese Erweiterung zu verwenden. Es ist sehr schön. https://efbulkinsert.codeplex.com/

0

Ok, die Lösung, die ich gefunden habe (wenn auch nicht sicher über die Begründung). Wenn ich nehmen Sie nicht die Entities Element in die Funktion für die Protokollierung und ich eine neue Instanz der aus dem EDMX erzeugt Entity e.g.:-

using(DBEntities db = new DBEntities()) { 
    importLog.SuccessfullyImportedRows = successfulRows; 
    importLog.FailedImportedRows = failedRows; 
    importLog.EndTime = DateTime.Now; 

    db.SaveChanges(); 
} 

Das in weniger arbeitet als eine Sekunde. Wird etwas in der ursprünglichen Instanz von DBEntities zwischengespeichert, wenn so viele Zeilen eingefügt werden?