web-dev-qa-db-de.com

Schnellste Einfügung in Entity Framework

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.

582
Bongo Sharp

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:

  • Rufe SaveChanges() einmal nach ALLEN Datensätzen auf.
  • Rufen Sie SaveChanges() nach beispielsweise 100 Datensätzen auf.
  • Rufen Sie SaveChanges() nach beispielsweise 100 Datensätzen auf, setzen Sie den Kontext ab und erstellen Sie einen neuen.
  • Deaktivieren Sie die Änderungserkennung

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:

  • commitCount = 1, recreateContext = false: viele Stunden (Dies ist Ihre aktuelle Prozedur)
  • commitCount = 100, recreateContext = false: länger als 20 Minuten
  • commitCount = 1000, recreateContext = false: 242 Sek.
  • commitCount = 10000, recreateContext = false: 202 s
  • commitCount = 100000, recreateContext = false: 199 s
  • commitCount = 1000000, recreateContext = false: Keine Speicherausnahme
  • commitCount = 1, recreateContext = true: länger als 10 Minuten
  • commitCount = 10, recreateContext = true: 241 Sek.
  • commitCount = 100, recreateContext = true: 164 Sek.
  • commitCount = 1000, recreateContext = true: 191 Sek.

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.

886
Slauma

Diese Kombination erhöht die Geschwindigkeit ausreichend.

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

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 EntityFramework.BulkInsert vs EF AddRange

die Verwendung ist extrem einfach

context.BulkInsert(hugeAmountOfEntities);
98
maxlego

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.

72
Adam Rackis

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();
        }

    }
}
47
Irfons

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:

  • commitCount = 1, recreateContext = true: länger als 10 Minuten
  • commitCount = 10, recreateContext = true: 241 sek 
  • commitCount = 100, recreateContext = true: 164 sek
  • commitCount = 1000, recreateContext = true: 191 sek

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.

18
Admir Tuzović

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:

  1. Standard-EF (57 Minuten bis zum Hinzufügen von 30.000 Datensätzen)
  2. Ersetzen durch ADO.NET-Code (25 Sekunden für dieselben 30.000)
  3. Kontext aufgebläht - Halten Sie das aktive Kontextdiagramm klein, indem Sie für jede Arbeitseinheit einen neuen Kontext verwenden (30.000 Einfügungen dauern 33 Sekunden)
  4. Große Listen - AutoDetectChangesEnabled deaktivieren (verkürzt die Zeit auf etwa 20 Sekunden)
  5. Batching (bis zu 16 Sekunden)
  6. DbTable.AddRange () - (Leistung liegt im Bereich 12)
16
ShaTin

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. 

15
Mikael Eliasson

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

13
Stephen Ho

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);
6
Guilherme

Versuchen Sie, eine Stored Procedure zu verwenden, die eine XML der Daten abruft, die Sie einfügen möchten.

5
Maxim

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();
    }
}
4
Sgedda

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:

  • Z.EntityFramework.Extensions (Recommended)
  • EFUtilities
  • EntityFramework.BulkInsert

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:

  • Bulk SaveChanges
  • Bulk Insert
  • Bulk löschen
  • Bulk-Update
  • Massenverschmelzung

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;
});
3
Jonathan Magnan

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;
}
2
Amir Saniyan

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();
2
Reza Jenabi

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. 

2
Greg R Taylor

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.

2
Zoran Horvat

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)

1
Rafael A. M. S.

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.

1
Aleksa

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

1
anishMarokey

[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.

1
Michał Pilarek

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

1
Mohd Nadeem

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;
        }
    }
}
0
Philip Johnson

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);
0
Manfred Wippel

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. enter image description here

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));
    }
0
Marinpietri

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
0
Simon Hughes

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)
0
arun tiwari