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/

56voto

Irfons Punkte 591

Ich stimme mit Adam Rackis überein. SqlBulkCopy ist der schnellste Weg, um Massensätze von einer Datenquelle in eine andere zu übertragen. Ich habe auf diese Weise 20.000 Datensätze kopiert, und es dauerte weniger als 3 Sekunden. Sehen Sie sich das folgende Beispiel an.

public static void InsertIntoMembers(DataTable dataTable)
{           
    using (var connection = new SqlConnection(@"data source=;persist security info=True;user id=;password=;initial catalog=;MultipleActiveResultSets=True;App=EntityFramework"))
    {
        SqlTransaction transaction = null;
        connection.Open();
        try
        {
            transaction = connection.BeginTransaction();
            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = "Members";
                sqlBulkCopy.ColumnMappings.Add("Firstname", "Firstname");
                sqlBulkCopy.ColumnMappings.Add("Lastname", "Lastname");
                sqlBulkCopy.ColumnMappings.Add("DOB", "DOB");
                sqlBulkCopy.ColumnMappings.Add("Gender", "Gender");
                sqlBulkCopy.ColumnMappings.Add("Email", "Email");

                sqlBulkCopy.ColumnMappings.Add("Address1", "Address1");
                sqlBulkCopy.ColumnMappings.Add("Address2", "Address2");
                sqlBulkCopy.ColumnMappings.Add("Address3", "Address3");
                sqlBulkCopy.ColumnMappings.Add("Address4", "Address4");
                sqlBulkCopy.ColumnMappings.Add("Postcode", "Postcode");

                sqlBulkCopy.ColumnMappings.Add("MobileNumber", "MobileNumber");
                sqlBulkCopy.ColumnMappings.Add("TelephoneNumber", "TelephoneNumber");

                sqlBulkCopy.ColumnMappings.Add("Deleted", "Deleted");

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction.Rollback();
        }

    }
}

29voto

XavierAM Punkte 1317

[2019 Update] EF Core 3.1

Nach dem, was oben gesagt wurde, deaktivieren AutoDetectChangesEnabled in EF Core funktionierte perfekt: die Einfügezeit wurde durch 100 geteilt (von vielen Minuten auf ein paar Sekunden, 10k Datensätze mit Kreuztabellen Beziehungen)

Der aktualisierte Code lautet :

context.ChangeTracker.AutoDetectChangesEnabled = false;
foreach (IRecord record in records) {
    //Add records to your database        
}
context.ChangeTracker.DetectChanges();
context.SaveChanges();
context.ChangeTracker.AutoDetectChangesEnabled = true; //do not forget to re-enable

27voto

ShaTin Punkte 2755

Ich empfehle diesen Artikel über die Erstellung von Masseneinlagen mit EF.

Entity Framework und langsame Massen-INSERTs

Er erforscht diese Bereiche und vergleicht die Leistungen:

  1. Standard-EF (57 Minuten für das Hinzufügen von 30.000 Datensätzen)
  2. Ersetzen durch ADO.NET-Code (25 Sekunden für dieselben 30.000)
  3. Context Bloat- Halten Sie den aktiven Context Graph klein, indem Sie für jede Unit of Work einen neuen Context verwenden (dieselben 30.000 Einfügungen dauern 33 Sekunden)
  4. Große Listen - Deaktivieren Sie AutoDetectChangesEnabled (reduziert die Zeit auf etwa 20 Sekunden)
  5. Batching (bis zu 16 Sekunden)
  6. DbTable.AddRange() - (Leistung liegt im Bereich von 12)

22voto

Mikael Eliasson Punkte 4997

Wie andere Leute gesagt haben, ist SqlBulkCopy der Weg, es zu tun, wenn Sie wirklich gute Leistung einfügen möchten.

Die Implementierung ist etwas mühsam, aber es gibt Bibliotheken, die Ihnen dabei helfen können. Es gibt ein paar da draußen, aber ich werde schamlosplug meine eigene Bibliothek dieses Mal: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

Der einzige Code, den Sie benötigen, ist:

 using (var db = new YourDbContext())
 {
     EFBatchOperation.For(db, db.BlogPosts).InsertAll(list);
 }

Wie viel schneller ist es denn? Das ist schwer zu sagen, weil es von so vielen Faktoren abhängt: Computerleistung, Netzwerk, Objektgröße usw. usw. Die Leistungstests, die ich durchgeführt habe, deuten darauf hin, dass 25k Entitäten mit etwa 10s der Zeit eingefügt werden können. Standardmethode auf localhost, WENN Sie Ihre EF-Konfiguration wie in den anderen Antworten beschrieben optimieren. Mit EFUtilities dauert das etwa 300ms. Noch interessanter ist, dass ich mit dieser Methode etwa 3 Millionen Entitäten in weniger als 15 Sekunden gespeichert habe, im Durchschnitt etwa 200k Entitäten pro Sekunde.

Das einzige Problem ist natürlich, wenn Sie relevante Daten einfügen müssen. Dies kann effizient in Sql Server mit der obigen Methode getan werden, aber es erfordert Sie eine Id-Generierung-Strategie, die Sie Id's in der App-Code für die übergeordnete generieren lassen, so dass Sie die Fremdschlüssel festlegen können. Dies kann mit GUIDs oder etwas wie HiLo id Generation getan werden.

22voto

Admir Tuzović Punkte 10817

Ich habe mich mit Slaumas Antwort beschäftigt (die großartig ist, danke für die Idee, Mann), und ich habe die Stapelgröße reduziert, bis ich die optimale Geschwindigkeit erreicht habe. Mit Blick auf die Ergebnisse von Slauma:

  • 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

Es ist zu erkennen, dass sich die Geschwindigkeit beim Übergang von 1 auf 10 und von 10 auf 100 erhöht, aber von 100 auf 1000 sinkt die Einfügegeschwindigkeit wieder.

Ich habe mich also darauf konzentriert, was passiert, wenn Sie die Stapelgröße auf einen Wert zwischen 10 und 100 reduzieren, und hier sind meine Ergebnisse (ich verwende verschiedene Zeileninhalte, also sind meine Zeiten von unterschiedlichem Wert):

Quantity    | Batch size    | Interval
1000    1   3
10000   1   34
100000  1   368

1000    5   1
10000   5   12
100000  5   133

1000    10  1
10000   10  11
100000  10  101

1000    20  1
10000   20  9
100000  20  92

1000    27  0
10000   27  9
100000  27  92

1000    30  0
10000   30  9
100000  30  92

1000    35  1
10000   35  9
100000  35  94

1000    50  1
10000   50  10
100000  50  106

1000    100 1
10000   100 14
100000  100 141

Ausgehend von meinen Ergebnissen liegt das tatsächliche Optimum bei einem Wert von 30 für die Losgröße. Das ist weniger als 10 und 100. Das Problem ist, dass ich keine Ahnung habe, warum 30 optimal ist, noch konnte ich eine logische Erklärung dafür finden.

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