SaveChanges eines Entity Framework-Kontextes ist gerade bei vielen Änderungen oder vielen Inserts sehr langsam. Dieser Artikel zeigt Optimierungsmöglichkeiten auf.

Ich setze das Entity Framework 6 nur ganz selten ein. In einem aktuellen Projekt mussten viele Daten einmalig importiert werden und das Datenbankschema stand bereits fest. Ergo dachte ich mir, ich setze auf EF, generiere mir das Modell, parse die Daten (Anlieferung per JSON) und schreibe sie in die Datenbank.

Die Lösung war super schnell implementiert. Den ersten Importversuch habe ich nach 20 Minuten abgebrochen. Hochgerechnet hätte er an die 5-6 Stunden benötigt. Gut, immerhin sind es 6 Tabellen, die Beziehungen zueinander pflegen und einige Millionen Datensätze. Dennoch, das muss viel schneller gehen.

Detect Changes

Besonders großen Einfluss hat die Einstellung (context ist hier der verwendete DbContext)

context.Configuration.AutoDetectChangesEnabled = false;

Damit wird nicht auf etwaige Änderungen geprüft (d.h. kein Lesen von Daten und keine Prüfungen).

Context verwerfen

SaveChanges habe ich nur einmal nachdem ich alle Daten dem Kontext hinzugefügt hatte, aufgerufen. Das führte bei mir zu einer OutOfMemoryException. Daher habe ich das in kleinere Chunks unterteilt und dafür dann SaveChanges aufgerufen. Es empfiehlt sich dann, den Kontext zu verwerfen und neu zu erstellen. Dadurch wird dieser bereinigt und enthält keine Daten mehr. Umso mehr Daten dieser enthält, umso langsamer wird er:

context.Dispose();
context = new MyDbContext();
context.Configuration.AutoDetectChagnesEnabled = false;

Änderungen asynchron speichern

Eine besonders große Änderung hat dann folgende Verwendung gebracht:

context.SaveChangesAsync().Wait();

Dadurch werden die Änderungen asynchron in die Datenbank übertragen (sofern möglich), was natürlich eine gewaltige Menge an Zeit spart.

Fazit

Durch die gezeigten Verbesserungsmöglichkeiten konnte ich die Verarbeitungszeit auf knapp 10 Minuten reduzieren. Wer es noch schneller braucht, der solle auf ein Bulk-Insert umsteigen, muss dafür allerdings mehr implementieren. Für meinen Fall war es so ausreichend. Gutes

Über den Autor

Norbert Eder

Ich bin ein leidenschaftlicher Softwareentwickler und Fotograf. Mein Wissen und meine Gedanken teile ich nicht nur hier im Blog, sondern auch in Fachartikeln und Büchern.

5 Kommentare

  • SaveChangesAsync gibt doch wahrscheinlich einen Task zurück oder?
    Warum blockst du stattdessen den Thread anstatt cooperative zu awaiten?

    • Das würde mich auch interessieren, wieso das schneller sein soll. Der aktuelle Thread wartet doch so oder so durch das Wait.

    • Es wird gewartet, da im Anschluss der Context via `Dispose()` verworfen und neu erstellt wird, was ebenfalls der Performance zuträglich ist. Insgesamt ist `context.SaveChangesAsync().Wait()` in meinem Anwendungsfall (Schreiben von hierarchischen Daten mit vielen Tabellen) um ein Zigfaches schneller als `context.SaveChanges()`.

      • Hallo Norbert,

        und in deinem Szenario werden die Daten klassisch in einem Thread über das EF in die Datenbank geschrieben? Dann muss ich das nochmal ausprobieren, einer erster kurzer Test hat zu keiner Besserung geführt. Kannst du sagen was für einen Faktor diese Anpassung gebracht hat? Ganz grob würde mir reichen, denn du sagst ja „ein Zigfaches schneller“
        Gruß Henry
        Gruß Henry

        • Hallo Henry,

          ich bin damit um 90% runter. Eventuell hängt das aber auch wirklich an meiner Konstellation der Tabellen. Wenn ich Zeit finde, werde ich das allerdings austesten um den wahren Grund zu erfahren.

          lG;
          Norbert

Hinterlasse einen Kommentar