Ich suche nach dem schnellsten Weg zum Einfügen in Entity Framework.
Ich frage dies aufgrund des Szenarios, in dem Sie ein aktives TransactionScope haben und die Einfügung sehr groß ist (4000+). Dies kann möglicherweise länger als 10 Minuten dauern (Standardzeitlimit für Transaktionen). Dies führt zu einer unvollständigen Transaktion.
c#
sql
entity-framework
Bongo Sharp
quelle
quelle
Antworten:
Zu Ihrer Bemerkung in den Kommentaren zu Ihrer Frage:
Das ist das Schlimmste, was du tun kannst! Das Aufrufen
SaveChanges()
jedes Datensatzes verlangsamt Masseneinfügungen extrem. Ich würde ein paar einfache Tests durchführen, die sehr wahrscheinlich die Leistung verbessern werden:SaveChanges()
Einmal nach ALLEN Aufzeichnungen anrufen .SaveChanges()
zum Beispiel nach 100 Datensätzen an.SaveChanges()
zum Beispiel nach 100 Datensätzen auf, entsorgen Sie den Kontext und erstellen Sie einen neuen.Für Masseneinsätze arbeite und experimentiere ich mit einem Muster wie diesem:
Ich habe ein Testprogramm, das 560.000 Entitäten (9 Skalareigenschaften, keine Navigationseigenschaften) in die Datenbank einfügt. Mit diesem Code funktioniert es in weniger als 3 Minuten.
Für die Aufführung ist es wichtig,
SaveChanges()
nach "vielen" Datensätzen aufzurufen ("viele" um 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
tut dies nicht, die Entitäten sind weiterhin an den Kontext im Status gebundenUnchanged
. Es ist die wachsende Größe der angehängten Entitäten im Kontext, die das Einfügen Schritt für Schritt verlangsamt. Es ist also hilfreich, es nach einiger Zeit zu löschen.Hier sind einige Messungen für meine 560000 Entitäten:
Das Verhalten im ersten Test oben ist, dass die Leistung sehr 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 angehalten.) Dieses nichtlineare Verhalten ist bei allen anderen Tests nicht so signifikant.
quelle
AutoDetectChangesEnabled = false;
den DbContext festzulegen. Es hat auch einen großen zusätzlichen Leistungseffekt: stackoverflow.com/questions/5943394/…DbContext
NICHTObjectContext
?Diese Kombination erhöht die Geschwindigkeit gut genug.
quelle
Der schnellste Weg wäre die Verwendung der von mir entwickelten Bulk-Insert-Erweiterung
Hinweis: Dies ist ein kommerzielles Produkt, das nicht kostenlos ist
Es verwendet SqlBulkCopy und einen benutzerdefinierten Datenleser, um maximale Leistung zu erzielen. Infolgedessen ist es über 20-mal schneller als die Verwendung von normalem Einfügen oder AddRange
Die Verwendung ist sehr einfach
quelle
Sie sollten sich die Verwendung der
System.Data.SqlClient.SqlBulkCopy
dafür ansehen . Hier ist die Dokumentation , und natürlich sind viele Tutorials online.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.
quelle
Ich stimme Adam Rackis zu.
SqlBulkCopy
ist der schnellste Weg, um Massendatensätze von einer Datenquelle zu einer anderen zu übertragen. Ich habe damit 20.000 Datensätze kopiert und es dauerte weniger als 3 Sekunden. Schauen Sie sich das folgende Beispiel an.quelle
AsDataReader()
Erweiterungsmethode, die in dieser Antwort erklärt wird: stackoverflow.com/a/36817205/1507899Ich würde diesen Artikel empfehlen, wie man Masseneinfügungen mit EF macht.
Entity Framework und langsame Massen-INSERTs
Er erkundet diese Bereiche und vergleicht die Leistung:
quelle
Da es hier nie erwähnt wurde, möchte ich EFCore.BulkExtensions hier weiterempfehlen
quelle
Ich habe Slaumas Antwort untersucht (was großartig ist, danke für den Ideengeber) und die Chargengröße reduziert, bis ich die optimale Geschwindigkeit erreicht habe. Betrachten Sie die Ergebnisse des Slauma:
Es ist sichtbar, dass die Geschwindigkeit beim Bewegen von 1 auf 10 und von 10 auf 100 zunimmt, die Einfügegeschwindigkeit von 100 auf 1000 jedoch wieder abnimmt.
Ich habe mich also darauf konzentriert, was passiert, wenn Sie die Stapelgröße auf einen Wert zwischen 10 und 100 reduzieren. Hier sind meine Ergebnisse (ich verwende unterschiedliche Zeileninhalte, daher sind meine Zeiten von unterschiedlichem Wert):
Basierend auf meinen Ergebnissen liegt das tatsächliche Optimum bei etwa 30 für die Chargengröße. Es ist weniger als 10 und 100. Das Problem ist, ich habe keine Ahnung, warum 30 optimal ist, noch hätte ich eine logische Erklärung dafür finden können.
quelle
Wie andere bereits gesagt haben, ist SqlBulkCopy der richtige Weg, wenn Sie eine wirklich gute Insert-Leistung wünschen.
Die Implementierung ist etwas umständlich, aber es gibt Bibliotheken, die Ihnen dabei helfen können. Es gibt einige, aber ich werde diesmal schamlos meine eigene Bibliothek anschließen: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities
Der einzige Code, den Sie benötigen würden, ist:
Wie viel schneller ist es? Sehr schwer zu sagen, da dies von so vielen Faktoren, der Computerleistung, dem Netzwerk, der Objektgröße usw. abhängt. Die von mir durchgeführten Leistungstests legen nahe, dass 25.000 Entitäten standardmäßig bei etwa 10 Sekunden eingefügt werden können bei localhost Sie Ihre EF-Konfiguration wie optimieren in den anderen Antworten erwähnt. Mit EFUtilities dauert das ungefähr 300ms. Noch interessanter ist, dass ich mit dieser Methode in weniger als 15 Sekunden ungefähr 3 Millionen Entitäten gespeichert habe, was einem Durchschnitt von ungefähr 200.000 Entitäten pro Sekunde entspricht.
Das einzige Problem ist natürlich, wenn Sie relevante Daten einfügen müssen. Dies kann mithilfe der oben beschriebenen Methode effizient in SQL Server durchgeführt werden. Sie benötigen jedoch eine ID-Generierungsstrategie, mit der Sie IDs im App-Code für das übergeordnete Element generieren können, damit Sie die Fremdschlüssel festlegen können. Dies kann mithilfe von GUIDs oder einer HiLo-ID-Generierung erfolgen.
quelle
EFBatchOperation
einen Konstruktor hätten, den Sie an übergebenDbContext
, anstatt an jede statische Methode zu übergeben. Generische Versionen vonInsertAll
und,UpdateAll
die die Sammlung automatisch finden, ähnlich wieDbContext.Set<T>
, wären auch gut.Dispose()
Kontext schafft Probleme, wenn die Entitäten,Add()
auf die Sie sich verlassen, andere vorinstallierte Entitäten (z. B. Navigationseigenschaften) im Kontext sindIch verwende ein ähnliches Konzept, um meinen Kontext klein zu halten und die gleiche Leistung zu erzielen
Aber anstatt
Dispose()
den Kontext zu erstellen und neu zu erstellen, entferne ich einfach die Entitäten, die bereits vorhanden sindSaveChanges()
Wickeln Sie es mit try catch ein und zeigen
TrasactionScope()
Sie sie bei Bedarf nicht hier an, um den Code sauber zu haltenquelle
Ich weiß, dass dies eine sehr alte Frage ist, aber einer hier sagte, er habe eine Erweiterungsmethode für die Verwendung von Masseneinfügungen mit EF entwickelt, und als ich nachschaute, stellte ich fest, dass die Bibliothek heute 599 US-Dollar kostet (für einen Entwickler). Vielleicht macht es Sinn für die gesamte Bibliothek, aber nur für die Masseneinfügung ist dies zu viel.
Hier ist eine sehr einfache Erweiterungsmethode, die ich gemacht habe. Ich benutze das zuerst beim Pairing mit der Datenbank (nicht zuerst mit Code getestet, aber ich denke, das funktioniert genauso). Ändern Sie
YourEntities
mit dem Namen Ihres Kontexts:Sie können dies für jede Sammlung verwenden, von der Folgendes geerbt
IEnumerable
wird:quelle
await bulkCopy.WriteToServerAsync(table);
Versuchen Sie, eine gespeicherte Prozedur zu verwenden , die eine XML-Datei der Daten abruft, die Sie einfügen möchten.
quelle
Ich habe eine generische Erweiterung des obigen Beispiels von @Slauma vorgenommen.
Verwendungszweck:
quelle
Es sind einige Bibliotheken von Drittanbietern verfügbar, die Bulk Insert unterstützen:
Siehe: Entity Framework Bulk Insert-Bibliothek
Seien Sie vorsichtig, wenn Sie eine Bulk-Insert-Bibliothek auswählen. Nur Entity Framework-Erweiterungen unterstützen alle Arten von Zuordnungen und Vererbungen und es ist die einzige, die noch unterstützt wird.
Haftungsausschluss : Ich bin der Eigentümer von Entity Framework Extensions
Mit dieser Bibliothek können Sie alle Massenoperationen ausführen, die Sie für Ihre Szenarien benötigen:
Beispiel
quelle
Verwendung
SqlBulkCopy
:quelle
Als eine der schnellsten Möglichkeiten zum Speichern einer Liste müssen Sie den folgenden Code anwenden
AutoDetectChangesEnabled = false
Add, AddRange & SaveChanges: Erkennt keine Änderungen.
ValidateOnSaveEnabled = false;
Ändert den Tracker nicht
Sie müssen Nuget hinzufügen
Jetzt können Sie den folgenden Code verwenden
quelle
SqlBulkCopy ist super schnell
Dies ist meine Implementierung:
quelle
[Update 2019] EF Core 3.1
Nach dem oben Gesagten funktionierte das Deaktivieren von AutoDetectChangesEnabled in EF Core einwandfrei: Die Einfügezeit wurde durch 100 geteilt (von vielen Minuten bis zu einigen Sekunden, 10.000 Datensätze mit tabellenübergreifenden Beziehungen).
Der aktualisierte Code lautet:
quelle
Hier ist ein Leistungsvergleich zwischen der Verwendung von Entity Framework und der Verwendung der SqlBulkCopy-Klasse anhand eines realistischen Beispiels: So fügen Sie komplexe Objekte in großen Mengen in die SQL Server-Datenbank ein
Wie andere bereits betont haben, sind ORMs nicht für den Massenbetrieb gedacht. Sie bieten Flexibilität, Trennung von Bedenken und andere Vorteile, aber Massenoperationen (außer Massenlesen) gehören nicht dazu.
quelle
Eine weitere Option ist die Verwendung von SqlBulkTools, die bei Nuget erhältlich sind. Es ist sehr einfach zu bedienen und verfügt über einige leistungsstarke Funktionen.
Beispiel:
Weitere Beispiele und erweiterte Verwendung finden Sie in der Dokumentation . Haftungsausschluss: Ich bin der Autor dieser Bibliothek und alle Ansichten sind meiner eigenen Meinung.
quelle
Nach meinem Wissen gibt es
no BulkInsert
inEntityFramework
der Leistung der großen Einsätze zu erhöhen.In diesem Szenario können Sie gehen mit SqlBulkCopy in
ADO.net
Ihr Problem zu lösenquelle
WriteToServer
, die eine dauertDataTable
.Haben Sie jemals versucht, über einen Hintergrundarbeiter oder eine Hintergrundaufgabe einzufügen?
In meinem Fall füge ich 7760 Register ein, die in 182 verschiedenen Tabellen mit Fremdschlüsselbeziehungen (von NavigationProperties) verteilt sind.
Ohne die Aufgabe dauerte es zweieinhalb Minuten. Innerhalb einer Aufgabe (
Task.Factory.StartNew(...)
) dauerte es 15 Sekunden.Ich mache das erst,
SaveChanges()
nachdem ich alle Entitäten zum Kontext hinzugefügt habe. (um die Datenintegrität sicherzustellen)quelle
Alle hier beschriebenen Lösungen helfen nicht weiter, denn wenn Sie SaveChanges () ausführen, werden Einfügeanweisungen einzeln an die Datenbank gesendet. So funktioniert Entity.
Wenn Ihre Reise zur Datenbank und zurück beispielsweise 50 ms beträgt, beträgt die zum Einfügen benötigte Zeit die Anzahl der Datensätze x 50 ms.
Sie müssen BulkInsert verwenden, hier ist der Link: https://efbulkinsert.codeplex.com/
Ich habe die Einfügezeit von 5-6 Minuten auf 10-12 Sekunden reduziert.
quelle
Sie können ein Massenpaket verwenden verwenden. Die Version Bulk Insert 1.0.0 wird in Projekten mit Entity Framework> = 6.0.0 verwendet.
Weitere Beschreibungen finden Sie hier - Quellcode für Massenoperationen
quelle
[NEUE LÖSUNG FÜR POSTGRESQL] Hey, ich weiß, dass es ein ziemlich alter Beitrag ist, aber ich bin kürzlich auf ein ähnliches Problem gestoßen, aber wir haben Postgresql verwendet. Ich wollte effektives Bulkinsert verwenden, was sich als ziemlich schwierig herausstellte. Ich habe in dieser Datenbank keine richtige freie Bibliothek dafür gefunden. Ich habe nur diesen Helfer gefunden: https://bytefish.de/blog/postgresql_bulk_insert/, der sich ebenfalls auf Nuget befindet. Ich habe einen kleinen Mapper geschrieben, der Eigenschaften wie Entity Framework automatisch zuordnet:
Ich benutze es folgendermaßen (ich hatte eine Entität namens Undertaking):
Ich habe ein Beispiel mit Transaktion gezeigt, aber es kann auch mit einer normalen Verbindung durchgeführt werden, die aus dem Kontext abgerufen wird. commitakingsToAdd enthält eine Auflistung normaler Entitätsdatensätze, die ich in die Datenbank einfügen möchte.
Diese Lösung, die ich nach ein paar Stunden Recherche und Ausprobieren habe, ist, wie Sie es erwarten können, viel schneller und endlich einfach zu bedienen und kostenlos! Ich rate Ihnen wirklich, diese Lösung zu verwenden, nicht nur aus den oben genannten Gründen, sondern auch, weil es die einzige ist, mit der ich keine Probleme mit Postgresql selbst hatte. Viele andere Lösungen funktionieren einwandfrei, zum Beispiel mit SqlServer.
quelle
Das Geheimnis besteht darin, in eine identische leere Staging-Tabelle einzufügen. Einsätze blitzen schnell. Führen Sie dann eine einzelne Einfügung davon in Ihre große Haupttabelle aus. Schneiden Sie dann die Staging-Tabelle ab, die für den nächsten Stapel bereit ist.
dh.
quelle
Für mehr als (+4000) Einfügungen empfehle ich jedoch die Verwendung einer gespeicherten Prozedur. fügte die verstrichene Zeit hinzu. Ich habe es 11.788 Zeilen in 20 "eingefügt.
das ist es Code
quelle
Verwenden Sie eine gespeicherte Prozedur, die Eingabedaten in Form von XML verwendet, um Daten einzufügen.
Fügen Sie aus Ihrem C # -Code-Pass Daten als XML ein.
zB in c # wäre die Syntax wie folgt:
quelle
Verwenden Sie diese Technik, um das Einfügen von Datensätzen in Entity Framework zu beschleunigen. Hier verwende ich eine einfache gespeicherte Prozedur, um die Datensätze einzufügen. Und um diese gespeicherte Prozedur auszuführen, verwende ich die .FromSql () -Methode von Entity Framework , die Raw SQL ausführt.
Der gespeicherte Prozedurcode:
Als nächstes durchlaufen Sie alle Ihre 4000 Datensätze und fügen Sie den Entity Framework-Code hinzu, der den gespeicherten ausführt
Die Prozedur beginnt jede 100. Schleife.
Zu diesem Zweck erstelle ich eine Zeichenfolgenabfrage, um diese Prozedur auszuführen. Hänge weiterhin alle Datensätze an.
Überprüfen Sie dann, ob die Schleife in Vielfachen von 100 ausgeführt wird, und führen Sie sie in diesem Fall mit aus
.FromSql()
.Überprüfen Sie den folgenden Code:
quelle