c# - net - entity framework tutorial deutsch




Schnellste Art des Einfügens in Entity Framework (16)

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

Es gibt einige Third-Party-Bibliotheken, die Bulk Insert unterstützen:

  • Z.EntityFramework.Extensions ( Empfohlen )
  • EFutilities
  • EntityFramework.BulkInsert

Siehe: Entity Framework-Masseneinfügungsbibliothek

Seien Sie vorsichtig, wenn Sie eine Masseneinsatzbibliothek auswählen. Nur Entity Framework Extensions unterstützen alle Arten von Assoziationen und Vererbung 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 Massenvorgänge ausführen, die Sie für Ihre Szenarien benötigen:

  • Bulk SaveÄnderungen
  • Masseneinfügung
  • Massenlöschung
  • Bulk-Update
  • Massenzusammenführung

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

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

Ich frage das wegen des Szenarios, in dem Sie ein aktives TransactionScope haben und die Einfügung ist riesig (4000+). Es kann möglicherweise länger als 10 Minuten dauern (Standard-Timeout von Transaktionen) und dies führt zu einer unvollständigen Transaktion.


Das Geheimnis besteht darin, in eine identische leere Zwischenspeichertabelle einzufügen. Einsätze sind blitzschnell. Führen Sie dann eine einzelne Einfügung von dieser in Ihre große Haupttabelle aus. Kürzen Sie dann die Zwischenspeichertabelle 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

Der schnellste Weg wäre die Verwendung der Bulk-Insert-Erweiterung , die ich entwickelt habe.

Es verwendet SqlBulkCopy und benutzerdefinierten Datenreader, um maximale Leistung zu erhalten. Als Ergebnis ist es über 20 mal schneller als normale Einfügung oder AddRange

Die Verwendung ist sehr einfach

context.BulkInsert(hugeAmountOfEntities);

Diese Kombination erhöht die Geschwindigkeit gut genug.

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

Haben Sie jemals versucht, durch einen Hintergrundarbeiter oder eine Aufgabe einzufügen?

In meinem Fall, im Einfügen von 7760 Registern, verteilt in 182 verschiedenen Tabellen mit Fremdschlüsselbeziehungen (von NavigationProperties).

Ohne die Aufgabe dauerte es zweieinhalb Minuten. Innerhalb einer Task ( Task.Factory.StartNew(...) ) dauerte es 15 Sekunden.

SaveChanges() mache nur die SaveChanges() nach dem Hinzufügen aller Entitäten zum Kontext. (um die Datenintegrität zu gewährleisten)


Ich habe die Antwort von Slauma untersucht (was super ist, danke für den Ideenmenschen), und ich habe die Losgröße reduziert, bis ich die optimale Geschwindigkeit erreicht habe. Blick auf die Ergebnisse von Slauma:

  • commitCount = 1, recreateContext = true: mehr als 10 Minuten
  • commitCount = 10, recreateContext = true: 241 Sek
  • commitCount = 100, recreateContext = true: 164 Sek
  • commitCount = 1000, recreateContext = true: 191 Sek

Es ist sichtbar, dass die Geschwindigkeit zunimmt, wenn von 1 auf 10 und von 10 auf 100 gewechselt wird, aber von 100 auf 1000 fällt die Einsetzgeschwindigkeit wieder ab.

Ich habe mich also darauf konzentriert, was passiert, wenn Sie die Batchgröße auf einen Wert zwischen 10 und 100 reduzieren, und hier sind meine Ergebnisse (ich verwende unterschiedliche Zeileninhalte, so dass meine Zeiten unterschiedlich sind):

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 bei 30 für die Chargengröße. Es ist weniger als 10 und 100. Das Problem ist, ich habe keine Ahnung, warum ist 30 optimal, noch konnte ich eine logische Erklärung dafür finden.


Ich stimme Adam Rackis zu. SqlBulkCopy ist die schnellste Methode zum Übertragen von Bulk-Datensätzen von einer Datenquelle zu einer anderen. Ich habe damit 20K-Datensätze kopiert und es dauerte weniger als 3 Sekunden. Sehen 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 würde diesen Artikel empfehlen, wie man Masseneinfügungen mit EF macht.

Entity Framework und langsame Bulk-INSERTs

Er erforscht diese Bereiche und vergleicht die Leistung:

  1. Standard-EF (57 Minuten zum Hinzufügen von 30.000 Datensätzen)
  2. Ersetzen mit ADO.NET Code (25 Sekunden für die gleichen 30.000)
  3. Context Bloat: Behalten Sie den aktiven Context Graph klein, indem Sie für jede Arbeitseinheit einen neuen Kontext verwenden (die gleichen 30.000 Inserts benötigen 33 Sekunden)
  4. Große Listen - Deaktivieren Sie AutoDetectChangesEnabled (verringert die Zeit auf ca. 20 Sekunden)
  5. Batching (bis zu 16 Sekunden)
  6. DbTable.AddRange () - (Leistung liegt im Bereich 12)

Im Folgenden finden Sie einen Leistungsvergleich zwischen der Verwendung von Entity Framework und der Verwendung der SqlBulkCopy-Klasse für ein realistisches Beispiel: So fügen Sie komplexe Objekte in der SQL Server-Datenbank in Massenform ein

Wie andere bereits betont haben, sind ORMs nicht für den Massenbetrieb gedacht. Sie bieten Flexibilität, Trennung von Anliegen und andere Vorteile, aber Bulk-Operationen (außer Bulk-Lesen) gehören nicht dazu.


Nach meinem Wissen gibt es no BulkInsert in EntityFramework , um die Leistung der riesigen Einsätze zu erhöhen.

In diesem Szenario können Sie mit documentation in ADO.net , um Ihr Problem zu lösen


Versuchen Sie eine Stored Procedure zu verwenden , die ein XML der Daten, die Sie einfügen möchten, erhält.


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

Zu Ihrer Bemerkung in den Kommentaren zu Ihrer Frage:

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

Das ist das Schlimmste, was du tun kannst! Durch den Aufruf von SaveChanges() für jeden Datensatz werden SaveChanges() extrem SaveChanges() . Ich würde ein paar einfache Tests machen, die sehr wahrscheinlich die Leistung verbessern werden:

  • Rufen Sie SaveChanges() einmal nach SaveChanges() Datensätzen auf.
  • Rufen Sie SaveChanges() nach beispielsweise 100 Datensätzen auf.
  • Rufen Sie SaveChanges() nach zB 100 SaveChanges() auf und disponieren Sie den Kontext und erstellen Sie einen neuen.
  • Deaktivieren Sie die Änderungserkennung

Für Masseneinsätze 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, SaveChanges() nach "vielen" Records ("viele" um 100 oder 1000) SaveChanges() . Es verbessert auch die Leistung, um den Kontext nach SaveChanges zu entfernen und einen neuen zu erstellen. Dies löscht den Kontext von allen Entitäten, SaveChanges macht das nicht, die Entitäten sind immer noch an den Kontext im Status Unchanged SaveChanges . Es ist die wachsende Größe von angehängten Entitäten im Kontext, was die Einfügung Schritt für Schritt verlangsamt. Es ist also hilfreich, es nach einiger Zeit zu löschen.

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

  • commitCount = 1, recreateContext = false: viele Stunden (Das ist Ihre aktuelle Prozedur)
  • commitCount = 100, recreateContext = false: mehr als 20 Minuten
  • commitCount = 1000, recreateContext = false: 242 Sek
  • commitCount = 10000, recreateContext = false: 202 Sek
  • commitCount = 100000, recreateContext = false: 199 Sek
  • commitCount = 1000000, recreateContext = false: Ausnahme wegen zu wenig Arbeitsspeicher
  • commitCount = 1, recreateContext = true: mehr 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 im Laufe der Zeit extrem abnimmt. ("Viele Stunden" ist eine Schätzung, ich habe diesen Test nie beendet, ich habe bei 50.000 Einheiten nach 20 Minuten angehalten.) Dieses nichtlineare Verhalten ist in allen anderen Tests nicht so signifikant.


All the solutions written here don't help because when you do SaveChanges(), insert statements are sent to database one by one, that's how Entity works.

And if your trip to database and back is 50 ms for instance then time needed for insert is number of records x 50 ms.

You have to use BulkInsert, here is the link: https://efbulkinsert.codeplex.com/

I got insert time reduced from 5-6 minutes to 10-12 seconds by using it.


Use stored procedure that takes input data in form of xml to insert data.

From your c# code pass insert data as xml.

eg in c#, syntax would be like this:

object id_application = db.ExecuteScalar("procSaveApplication", xml)







entity-framework