808 Stimmen

Der schnellste Weg zum Einfügen in Entity Framework

Ich bin auf der Suche nach dem schnellsten Weg zum Einfügen in Entity Framework.

Ich frage dies wegen des Szenarios, in dem Sie eine aktive TransactionScope und die Einfügung ist riesig (4000+). Sie kann möglicherweise mehr als 10 Minuten dauern (Standard-Timeout für Transaktionen), was zu einer unvollständigen Transaktion führt.

2 Stimmen

Wie machen Sie es derzeit?

1 Stimmen

Erstellen des TransactionScope, Instanziierung des DBContext, Öffnen der Verbindung, und in einer for-each-Anweisung die Einfügungen und SavingChanges (für jeden Datensatz), HINWEIS: TransactionScope und DBContext sind in using-Anweisungen, und ich schließe die Verbindung in einem finally-Block

1 Stimmen

Eine weitere Antwort als Referenz: stackoverflow.com/questions/5798646/

1125voto

Slauma Punkte 171348

Zu Ihrer Bemerkung in den Kommentaren zu Ihrer Frage:

"...SavingChanges ( für jeden Datensatz )..."

Das ist das Schlimmste, was man tun kann! Aufruf von SaveChanges() für jeden Datensatz verlangsamt Masseneinfügungen extrem. Ich würde ein paar einfache Tests durchführen, die sehr wahrscheinlich die Leistung verbessern werden:

  • Rufen Sie an. SaveChanges() einmal nach ALLEN Datensätzen.
  • Rufen Sie an. SaveChanges() nach zum Beispiel 100 Datensätzen.
  • Rufen Sie an. SaveChanges() nach z.B. 100 Datensätzen und entsorgen den Kontext und erstellen einen neuen.
  • Änderungserkennung deaktivieren

Für Masseneinlagen arbeite und experimentiere ich mit einem Muster wie diesem:

using (TransactionScope scope = new TransactionScope())
{
    MyDbContext context = null;
    try
    {
        context = new MyDbContext();
        context.Configuration.AutoDetectChangesEnabled = false;

        int count = 0;            
        foreach (var entityToInsert in someCollectionOfEntitiesToInsert)
        {
            ++count;
            context = AddToContext(context, entityToInsert, count, 100, true);
        }

        context.SaveChanges();
    }
    finally
    {
        if (context != null)
            context.Dispose();
    }

    scope.Complete();
}

private MyDbContext AddToContext(MyDbContext context,
    Entity entity, int count, int commitCount, bool recreateContext)
{
    context.Set<Entity>().Add(entity);

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

    return context;
}

Ich habe ein Testprogramm, das 560.000 Entitäten (9 skalare Eigenschaften, keine Navigationseigenschaften) in die DB einfügt. Mit diesem Code funktioniert es in weniger als 3 Minuten.

Für die Performance ist es wichtig, die SaveChanges() nach "vielen" Datensätzen ("viele" etwa 100 oder 1000). Es verbessert auch die Leistung, den Kontext nach SaveChanges zu entsorgen und einen neuen zu erstellen. Dies löscht den Kontext von allen Entitäten, SaveChanges das nicht tut, sind die Entitäten immer noch mit dem Kontext im Zustand Unchanged . Es ist die wachsende Größe der angehängten Entitäten im Kontext, die das Einfügen Schritt für Schritt verlangsamt. Daher ist es hilfreich, sie nach einiger Zeit zu löschen.

Hier sind ein paar Messungen für meine 560000 Einheiten:

  • commitCount = 1, recreateContext = false: viele Stunden (Das ist Ihr derzeitiges Verfahren)
  • commitCount = 100, recreateContext = false: mehr als 20 Minuten
  • commitCount = 1000, recreateContext = false: 242 sec
  • commitCount = 10000, recreateContext = false: 202 Sekunden
  • commitCount = 100000, recreateContext = false: 199 sec
  • commitCount = 1000000, recreateContext = false: Ausnahme wegen Speichermangels
  • commitCount = 1, recreateContext = true: mehr als 10 Minuten
  • commitCount = 10, recreateContext = true: 241 sec
  • commitCount = 100, recreateContext = true: 164 sec
  • commitCount = 1000, recreateContext = true: 191 sec

Der erste Test zeigt, dass die Leistung nicht linear ist und mit der Zeit extrem abnimmt. ("Viele Stunden" ist eine Schätzung, ich habe diesen Test nie beendet, ich habe nach 20 Minuten bei 50.000 Entitäten aufgehört). Dieses nichtlineare Verhalten ist bei allen anderen Tests nicht so signifikant.

191voto

arkhivania Punkte 2276

Diese Kombination erhöht die Geschwindigkeit in ausreichendem Maße.

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

99voto

Manfred Wippel Punkte 1606

Da es hier nie erwähnt wurde, möchte ich EFCore.BulkExtensions empfehlen aquí

context.BulkInsert(entitiesList);                 context.BulkInsertAsync(entitiesList);
context.BulkUpdate(entitiesList);                 context.BulkUpdateAsync(entitiesList);
context.BulkDelete(entitiesList);                 context.BulkDeleteAsync(entitiesList);
context.BulkInsertOrUpdate(entitiesList);         context.BulkInsertOrUpdateAsync(entitiesList);         // Upsert
context.BulkInsertOrUpdateOrDelete(entitiesList); context.BulkInsertOrUpdateOrDeleteAsync(entitiesList); // Sync
context.BulkRead(entitiesList);                   context.BulkReadAsync(entitiesList);

94voto

Adam Rackis Punkte 81499

Sie sollten prüfen, ob Sie die System.Data.SqlClient.SqlBulkCopy für diese. Hier ist die Dokumentation und natürlich gibt es zahlreiche Anleitungen im Internet.

Entschuldigung, ich weiß, dass Sie nach einer einfachen Antwort gesucht haben, um EF dazu zu bringen, das zu tun, was Sie wollen, aber Massenoperationen sind nicht wirklich das, wofür ORMs gedacht sind.

87voto

maxlego Punkte 4784

Der schnellste Weg wäre die Verwendung von Bulk Insert Extension die ich entwickelt habe

Hinweis: Dies ist ein kommerzielles Produkt, das nicht kostenlos ist.

Es verwendet SqlBulkCopy und benutzerdefinierte datareader, um maximale Leistung zu erhalten. Im Ergebnis ist es über 20 Mal schneller als die Verwendung von regulären Insert oder AddRange EntityFramework.BulkInsert vs EF AddRange

Die Verwendung ist sehr einfach

context.BulkInsert(hugeAmountOfEntities);

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X