Ich bin auf der Suche nach der schnellsten Methode zum Einfügen in das Entity Framework.
Ich frage dies wegen des Szenarios, in dem Sie einen aktiven TransactionScope haben und die Einfügung sehr groß ist (4000+). Es kann möglicherweise länger als 10 Minuten dauern (Standard-Timeout von Transaktionen) und dies führt zu einer unvollständigen Transaktion.
Zu Ihrer Bemerkung in den Kommentaren zu Ihrer Frage:
"... SavingChanges ( für jeden Datensatz ) ..."
Das ist das Schlimmste, was du tun kannst! Durch Aufruf von SaveChanges()
für jeden Datensatz werden Bulk-Inserts extrem verlangsamt. Ich würde ein paar einfache Tests durchführen, die sehr wahrscheinlich die Leistung verbessern werden:
SaveChanges()
einmal nach ALLEN Datensätzen auf.SaveChanges()
nach beispielsweise 100 Datensätzen auf.SaveChanges()
nach beispielsweise 100 Datensätzen auf, setzen Sie den Kontext ab und erstellen Sie einen neuen.Für Bulk-Inserts 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 Skalareigenschaften, keine Navigationseigenschaften) in die DB einfügt. Mit diesem Code funktioniert es in weniger als 3 Minuten.
Für die Leistung ist es wichtig, SaveChanges()
nach "vielen" Datensätzen ("viele" um 100 oder 1000) aufzurufen. Außerdem wird die Leistung verbessert, wenn der Kontext nach SaveChanges freigegeben und ein neuer erstellt wird. Dadurch wird der Kontext von allen Elementen gelöscht. SaveChanges
tut das nicht. Die Entitäten sind immer noch an den Kontext im Status Unchanged
gebunden. Es ist die wachsende Größe der angehängten Objekte im Kontext, die das Einfügen schrittweise verlangsamt. Daher ist es hilfreich, es nach einiger Zeit zu löschen.
Hier sind ein paar Messungen für meine 560.000 Entitäten:
Das Verhalten im ersten Test oben ist, dass die Leistung sehr nichtlinear 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 Einheiten gestoppt.) Dieses nichtlineare Verhalten ist in allen anderen Tests nicht so signifikant.
Diese Kombination erhöht die Geschwindigkeit ausreichend.
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
Der schnellste Weg wäre die Verwendung von bulk insert extension , die ich entwickelt habe.
Es verwendet SqlBulkCopy und einen benutzerdefinierten Datenbereich, um maximale Leistung zu erzielen. Infolgedessen ist es mehr als 20-mal schneller als mit regulärem Insert oder AddRange
die Verwendung ist extrem einfach
context.BulkInsert(hugeAmountOfEntities);
Sie sollten dafür den System.Data.SqlClient.SqlBulkCopy
verwenden. Hier ist die Dokumentation und natürlich gibt es 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, was ORMs sind.
Ich stimme Adam Rackis zu. SqlBulkCopy
ist der schnellste Weg, um Datensätze von einer Datenquelle in eine andere zu übertragen. Ich habe dies zum Kopieren von 20K-Datensätzen verwendet, und es dauerte weniger als 3 Sekunden. Schauen Sie sich das Beispiel unten 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();
}
}
}
Ich habe Slaumas Antwort untersucht (was großartig ist, danke für den Ideenmann), und ich habe die Losgröße reduziert, bis ich die optimale Geschwindigkeit erreicht habe. Ein Blick auf die Ergebnisse von Slauma:
Es ist zu sehen, dass die Geschwindigkeit zunimmt, wenn von 1 auf 10 und von 10 auf 100 bewegt wird, aber von 100 auf 1000 fällt die Einfügegeschwindigkeit wieder ab.
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):
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
Basierend auf meinen Ergebnissen liegt das tatsächliche Optimum für die Chargengröße bei etwa 30. 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.
Ich würde diesen Artikel empfehlen, wie Bulk-Inserts mit EF ausgeführt werden.
Entity Framework und langsame Bulk-INSERTs
Er erforscht diese Bereiche und vergleicht Leistung:
Wie andere Leute gesagt haben, ist SqlBulkCopy der richtige Weg, wenn Sie wirklich gute Einfügeleistung wünschen.
Die Implementierung ist etwas umständlich, aber es gibt Bibliotheken, die Ihnen dabei helfen können. Es gibt ein paar da draußen, aber ich werde diesmal meine eigene Bibliothek schamlos pluggen: 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 also? Sehr schwer zu sagen, da dies von so vielen Faktoren abhängt, von der Computerleistung, dem Netzwerk, der Objektgröße usw. usw. Bei den von mir durchgeführten Leistungstests können 25k-Entitäten bei ca. 10s in die Standard-Methode auf localhost eingefügt werden Ihre EF-Konfiguration wie 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 rund 3 Millionen Objekte gespart habe, im Durchschnitt etwa 200.000 Objekte pro Sekunde.
Das eine Problem ist natürlich, wenn Sie wiedergegebene Daten einfügen müssen. Dies kann mit der oben beschriebenen Methode effizient auf dem SQL-Server durchgeführt werden, es ist jedoch eine Strategie zur Generierung der ID erforderlich, mit der Sie IDs im App-Code für den übergeordneten Code generieren können, um die Fremdschlüssel festzulegen. Dies kann mithilfe von GUIDs oder einer ähnlichen Funktion wie der Generierung der HiLo-ID erfolgen.
Dispose()
context verursacht Probleme, wenn die Entitäten, die Sie Add()
verwenden, im Kontext von anderen vorgeladenen Entitäten (z. B. Navigationseigenschaften) abhängen
Ich verwende ein ähnliches Konzept, um meinen Kontext klein zu halten, um dieselbe Leistung zu erzielen
Aber anstelle von Dispose()
dem Kontext und der Neuerstellung entferne ich einfach die Entitäten, die bereits SaveChanges()
sind.
public void AddAndSave<TEntity>(List<TEntity> entities) where TEntity : class {
const int CommitCount = 1000; //set your own best performance number here
int currentCount = 0;
while (currentCount < entities.Count())
{
//make sure it don't commit more than the entities you have
int commitCount = CommitCount;
if ((entities.Count - currentCount) < commitCount)
commitCount = entities.Count - currentCount;
//e.g. Add entities [ i = 0 to 999, 1000 to 1999, ... , n to n+999... ] to conext
for (int i = currentCount; i < (currentCount + commitCount); i++)
_context.Entry(entities[i]).State = System.Data.EntityState.Added;
//same as calling _context.Set<TEntity>().Add(entities[i]);
//commit entities[n to n+999] to database
_context.SaveChanges();
//detach all entities in the context that committed to database
//so it won't overload the context
for (int i = currentCount; i < (currentCount + commitCount); i++)
_context.Entry(entities[i]).State = System.Data.EntityState.Detached;
currentCount += commitCount;
} }
wickeln Sie es mit try catch und TrasactionScope()
ein, wenn Sie möchten..... Sie werden hier nicht angezeigt, um den Code sauber zu halten
Ich weiß, dass dies eine sehr alte Frage ist, aber einer sagte, dass eine Erweiterungsmethode entwickelt wurde, um Bulk Insert mit EF zu verwenden, und als ich das überprüfte, entdeckte ich, dass die Bibliothek heute 599 Dollar kostet (für einen Entwickler). Vielleicht macht es für die gesamte Bibliothek Sinn, aber nur für den Masseneinsatz ist dies zu viel.
Hier ist eine sehr einfache Erweiterungsmethode, die ich erstellt habe. Ich benutze das Paar zuerst mit der Datenbank (nicht mit Code getestet, aber ich denke, das funktioniert genauso). Ändern Sie YourEntities
mit dem Namen Ihres Kontexts:
public partial class YourEntities : DbContext
{
public async Task BulkInsertAllAsync<T>(IEnumerable<T> entities)
{
using (var conn = new SqlConnection(Database.Connection.ConnectionString))
{
await conn.OpenAsync();
Type t = typeof(T);
var bulkCopy = new SqlBulkCopy(conn)
{
DestinationTableName = GetTableName(t)
};
var table = new DataTable();
var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));
foreach (var property in properties)
{
Type propertyType = property.PropertyType;
if (propertyType.IsGenericType &&
propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
propertyType = Nullable.GetUnderlyingType(propertyType);
}
table.Columns.Add(new DataColumn(property.Name, propertyType));
}
foreach (var entity in entities)
{
table.Rows.Add(
properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
}
bulkCopy.BulkCopyTimeout = 0;
await bulkCopy.WriteToServerAsync(table);
}
}
public void BulkInsertAll<T>(IEnumerable<T> entities)
{
using (var conn = new SqlConnection(Database.Connection.ConnectionString))
{
conn.Open();
Type t = typeof(T);
var bulkCopy = new SqlBulkCopy(conn)
{
DestinationTableName = GetTableName(t)
};
var table = new DataTable();
var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));
foreach (var property in properties)
{
Type propertyType = property.PropertyType;
if (propertyType.IsGenericType &&
propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
propertyType = Nullable.GetUnderlyingType(propertyType);
}
table.Columns.Add(new DataColumn(property.Name, propertyType));
}
foreach (var entity in entities)
{
table.Rows.Add(
properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
}
bulkCopy.BulkCopyTimeout = 0;
bulkCopy.WriteToServer(table);
}
}
public string GetTableName(Type type)
{
var metadata = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;
var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));
var entityType = metadata
.GetItems<EntityType>(DataSpace.OSpace)
.Single(e => objectItemCollection.GetClrType(e) == type);
var entitySet = metadata
.GetItems<EntityContainer>(DataSpace.CSpace)
.Single()
.EntitySets
.Single(s => s.ElementType.Name == entityType.Name);
var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace)
.Single()
.EntitySetMappings
.Single(s => s.EntitySet == entitySet);
var table = mapping
.EntityTypeMappings.Single()
.Fragments.Single()
.StoreEntitySet;
return (string)table.MetadataProperties["Table"].Value ?? table.Name;
}
}
Sie können das für jede Sammlung verwenden, die von IEnumerable
erbt, wie folgt:
await context.BulkInsertAllAsync(items);
Versuchen Sie, eine Stored Procedure zu verwenden, die eine XML der Daten abruft, die Sie einfügen möchten.
Ich habe eine generische Erweiterung des obigen Beispiels von @Slauma vorgenommen.
public static class DataExtensions
{
public static DbContext AddToContext<T>(this DbContext context, object entity, int count, int commitCount, bool recreateContext, Func<DbContext> contextCreator)
{
context.Set(typeof(T)).Add((T)entity);
if (count % commitCount == 0)
{
context.SaveChanges();
if (recreateContext)
{
context.Dispose();
context = contextCreator.Invoke();
context.Configuration.AutoDetectChangesEnabled = false;
}
}
return context;
}
}
Verwendungszweck:
public void AddEntities(List<YourEntity> entities)
{
using (var transactionScope = new TransactionScope())
{
DbContext context = new YourContext();
int count = 0;
foreach (var entity in entities)
{
++count;
context = context.AddToContext<TenancyNote>(entity, count, 100, true,
() => new YourContext());
}
context.SaveChanges();
transactionScope.Complete();
}
}
Ich bin auf der Suche nach der schnellsten Methode zum Einfügen in das Entity Framework
Es gibt einige Bibliotheken von Drittanbietern, 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 Extensions unterstützt alle Arten von Assoziationen und Vererbungen. Dies ist die einzige, die noch unterstützt wird.
Disclaimer: Ich bin Inhaber von Entity Framework Extensions
Mit dieser Bibliothek können Sie alle Massenvorgänge ausführen, die Sie für Ihre Szenarien benötigen:
Beispiel
// Easy to use
context.BulkSaveChanges();
// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);
// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);
// Customize Primary Key
context.BulkMerge(customers, operation => {
operation.ColumnPrimaryKeyExpression =
customer => customer.Code;
});
Verwenden Sie SqlBulkCopy
:
void BulkInsert(GpsReceiverTrack[] gpsReceiverTracks)
{
if (gpsReceiverTracks == null)
{
throw new ArgumentNullException(nameof(gpsReceiverTracks));
}
DataTable dataTable = new DataTable("GpsReceiverTracks");
dataTable.Columns.Add("ID", typeof(int));
dataTable.Columns.Add("DownloadedTrackID", typeof(int));
dataTable.Columns.Add("Time", typeof(TimeSpan));
dataTable.Columns.Add("Latitude", typeof(double));
dataTable.Columns.Add("Longitude", typeof(double));
dataTable.Columns.Add("Altitude", typeof(double));
for (int i = 0; i < gpsReceiverTracks.Length; i++)
{
dataTable.Rows.Add
(
new object[]
{
gpsReceiverTracks[i].ID,
gpsReceiverTracks[i].DownloadedTrackID,
gpsReceiverTracks[i].Time,
gpsReceiverTracks[i].Latitude,
gpsReceiverTracks[i].Longitude,
gpsReceiverTracks[i].Altitude
}
);
}
string connectionString = (new TeamTrackerEntities()).Database.Connection.ConnectionString;
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
{
sqlBulkCopy.DestinationTableName = dataTable.TableName;
foreach (DataColumn column in dataTable.Columns)
{
sqlBulkCopy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
}
sqlBulkCopy.WriteToServer(dataTable);
}
transaction.Commit();
}
}
return;
}
Eine der schnellsten Methoden, um eine Liste zu speichern
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
AutoDetectChangesEnabled = false
Add, AddRange & SaveChanges: Erkennt Änderungen nicht.
ValidateOnSaveEnabled = false;
Änderungs-Tracker wird nicht erkannt
Sie müssen Nuget hinzufügen
Install-Package Z.EntityFramework.Extensions
Jetzt können Sie den folgenden Code verwenden
var context = new MyContext();
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
context.BulkInsert(list);
context.BulkSaveChanges();
Eine weitere Option ist die Verwendung der von Nuget verfügbaren SqlBulkTools. Es ist sehr einfach zu bedienen und verfügt über einige leistungsstarke Funktionen.
Beispiel:
var bulk = new BulkOperations();
var books = GetBooks();
using (TransactionScope trans = new TransactionScope())
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager
.ConnectionStrings["SqlBulkToolsTest"].ConnectionString))
{
bulk.Setup<Book>()
.ForCollection(books)
.WithTable("Books")
.AddAllColumns()
.BulkInsert()
.Commit(conn);
}
trans.Complete();
}
Siehe die Dokumentation für weitere Beispiele und erweiterte Verwendung. Haftungsausschluss: Ich bin der Autor dieser Bibliothek und alle Ansichten sind meiner Meinung nach.
Hier ist ein Leistungsvergleich zwischen der Verwendung von Entity Framework und der Verwendung der SqlBulkCopy-Klasse in einem realistischen Beispiel: So fügen Sie komplexe Objekte in die SQL Server-Datenbank ein
Wie bereits erwähnt, sind ORMs nicht für den Einsatz in Massenvorgängen gedacht. Sie bieten Flexibilität, Trennung von Anliegen und anderen Vorteilen, aber Massenoperationen (außer Massenlesen) gehören nicht dazu.
Haben Sie schon einmal versucht, durch einen Hintergrundarbeiter oder eine Aufgabe einzufügen?
In meinem Fall füge ich 7760 Register ein, die in 182 verschiedenen Tabellen mit Fremdschlüsselbeziehungen (durch NavigationProperties) verteilt sind.
Ohne die Aufgabe dauerte es eineinhalb Minuten. Innerhalb einer Aufgabe (Task.Factory.StartNew(...)
) dauerte es 15 Sekunden.
Ich mache die SaveChanges()
erst nachdem ich alle Entitäten zum Kontext hinzugefügt habe. (zur Gewährleistung der Datenintegrität)
Alle hier geschriebenen Lösungen helfen nicht, denn wenn Sie SaveChanges () ausführen, werden Einfügeanweisungen nacheinander an die Datenbank gesendet. So funktioniert Entity.
Und wenn Ihre Fahrt zur Datenbank und zurück beispielsweise 50 ms beträgt, ist die Zeit, die für das Einfügen benötigt wird, die Anzahl der Datensätze x 50 ms.
Sie müssen BulkInsert verwenden, hier ist der Link: https://efbulkinsert.codeplex.com/
Die Insert-Zeit wurde durch Verwendung von 5-6 Minuten auf 10-12 Sekunden verkürzt.
Meines Wissens nach gibt es no BulkInsert
in EntityFramework
, um die Leistung der großen Einsätze zu erhöhen.
In diesem Szenario können Sie mit SqlBulkCopy in ADO.net
Ihr Problem lösen
[NEUE LÖSUNG FÜR POSTGRESQL] Hey, ich weiß, es ist ein ziemlich alter Beitrag, aber ich habe kürzlich ein ähnliches Problem, aber wir haben Postgresql verwendet. Ich wollte effektives Bulkinsert verwenden, was sich als ziemlich schwierig herausstellte. Ich habe keine passende freie Bibliothek gefunden, um dies in dieser Datenbank zu tun. Ich habe nur diesen Helfer gefunden: https://bytefish.de/blog/postgresql_bulk_insert/ Das sich auch auf Nuget befindet. Ich habe einen kleinen Mapper geschrieben, der Eigenschaften wie Entity Framework automatisch mappte:
public static PostgreSQLCopyHelper<T> CreateHelper<T>(string schemaName, string tableName)
{
var helper = new PostgreSQLCopyHelper<T>("dbo", "\"" + tableName + "\"");
var properties = typeof(T).GetProperties();
foreach(var prop in properties)
{
var type = prop.PropertyType;
if (Attribute.IsDefined(prop, typeof(KeyAttribute)) || Attribute.IsDefined(prop, typeof(ForeignKeyAttribute)))
continue;
switch (type)
{
case Type intType when intType == typeof(int) || intType == typeof(int?):
{
helper = helper.MapInteger("\"" + prop.Name + "\"", x => (int?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
break;
}
case Type stringType when stringType == typeof(string):
{
helper = helper.MapText("\"" + prop.Name + "\"", x => (string)typeof(T).GetProperty(prop.Name).GetValue(x, null));
break;
}
case Type dateType when dateType == typeof(DateTime) || dateType == typeof(DateTime?):
{
helper = helper.MapTimeStamp("\"" + prop.Name + "\"", x => (DateTime?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
break;
}
case Type decimalType when decimalType == typeof(decimal) || decimalType == typeof(decimal?):
{
helper = helper.MapMoney("\"" + prop.Name + "\"", x => (decimal?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
break;
}
case Type doubleType when doubleType == typeof(double) || doubleType == typeof(double?):
{
helper = helper.MapDouble("\"" + prop.Name + "\"", x => (double?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
break;
}
case Type floatType when floatType == typeof(float) || floatType == typeof(float?):
{
helper = helper.MapReal("\"" + prop.Name + "\"", x => (float?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
break;
}
case Type guidType when guidType == typeof(Guid):
{
helper = helper.MapUUID("\"" + prop.Name + "\"", x => (Guid)typeof(T).GetProperty(prop.Name).GetValue(x, null));
break;
}
}
}
return helper;
}
Ich benutze es auf folgende Weise (ich hatte eine Entität namens Undertaking):
var undertakingHelper = BulkMapper.CreateHelper<Model.Undertaking>("dbo", nameof(Model.Undertaking));
undertakingHelper.SaveAll(transaction.UnderlyingTransaction.Connection as Npgsql.NpgsqlConnection, undertakingsToAdd));
Ich habe ein Beispiel mit Transaktion gezeigt, aber es kann auch mit einer normalen Verbindung aus dem Kontext ausgeführt werden. undertakingsToAdd ist mit normalen Entitätsdatensätzen versehen, die ich in die Datenbank einfügen möchte.
Diese Lösung, die ich nach ein paar Stunden Recherche und Versuch erhalten habe, ist, wie man es erwarten könnte, viel schneller und endlich einfach zu benutzen und kostenlos! Ich empfehle Ihnen wirklich, diese Lösung zu verwenden, nicht nur aus den oben genannten Gründen, sondern auch, weil es das einzige ist, bei dem ich mit Postgresql selbst keine Probleme hatte. Viele andere Lösungen funktionieren beispielsweise mit SqlServer einwandfrei.
Sie können Bulk package library verwenden. Die Bulk-Insert-Version 1.0.0 wird in Projekten mit Entity-Framework> = 6.0.0 verwendet.
Weitere Beschreibung finden Sie hier- Quellcode für Bulkoperationen
SqlBulkCopy ist super schnell
Dies ist meine Implementierung:
// at some point in my calling code, I will call:
var myDataTable = CreateMyDataTable();
myDataTable.Rows.Add(Guid.NewGuid,tableHeaderId,theName,theValue); // e.g. - need this call for each row to insert
var efConnectionString = ConfigurationManager.ConnectionStrings["MyWebConfigEfConnection"].ConnectionString;
var efConnectionStringBuilder = new EntityConnectionStringBuilder(efConnectionString);
var connectionString = efConnectionStringBuilder.ProviderConnectionString;
BulkInsert(connectionString, myDataTable);
private DataTable CreateMyDataTable()
{
var myDataTable = new DataTable { TableName = "MyTable"};
// this table has an identity column - don't need to specify that
myDataTable.Columns.Add("MyTableRecordGuid", typeof(Guid));
myDataTable.Columns.Add("MyTableHeaderId", typeof(int));
myDataTable.Columns.Add("ColumnName", typeof(string));
myDataTable.Columns.Add("ColumnValue", typeof(string));
return myDataTable;
}
private void BulkInsert(string connectionString, DataTable dataTable)
{
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
SqlTransaction transaction = null;
try
{
transaction = connection.BeginTransaction();
using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
{
sqlBulkCopy.DestinationTableName = dataTable.TableName;
foreach (DataColumn column in dataTable.Columns) {
sqlBulkCopy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
}
sqlBulkCopy.WriteToServer(dataTable);
}
transaction.Commit();
}
catch (Exception)
{
transaction?.Rollback();
throw;
}
}
}
da es hier nie erwähnt wurde, möchte ich EFCore.BulkExtensions here weiterempfehlen.
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);
Bei mehr als (+4000) Einfügungen empfehle ich jedoch, gespeicherte Prozeduren zu verwenden. beigefügt die abgelaufene Zeit . Ich habe es 11.788 Zeilen in 20 " eingefügt.
das ist es Code
public void InsertDataBase(MyEntity entity)
{
repository.Database.ExecuteSqlCommand("sp_mystored " +
"@param1, @param2"
new SqlParameter("@param1", entity.property1),
new SqlParameter("@param2", entity.property2));
}
Das Geheimnis ist das Einfügen in eine identische leere Staging-Tabelle. Inserts blitzen schnell auf. Führen Sie dann einen single - Insert in Ihre große große Tabelle ein. Dann kürzen Sie die Staging-Tabelle für den nächsten Stapel.
dh.
insert into some_staging_table using Entity Framework.
-- Single insert into main table (this could be a tiny stored proc call)
insert into some_main_already_large_table (columns...)
select (columns...) from some_staging_table
truncate table some_staging_table
Verwenden Sie die gespeicherte Prozedur, die Eingabedaten in Form von XML zum Einfügen von Daten verwendet.
Fügen Sie aus Ihrem C # -Code die Daten als XML ein.
in c # wäre die Syntax beispielsweise so:
object id_application = db.ExecuteScalar("procSaveApplication", xml)