it-swarm.com.de

Verbessern der Leistung von Masseneinfügungen in Entity Framework

Ich möchte 20000 Datensätze in eine Tabelle nach Entity Framework einfügen, und es dauert ungefähr 2 Minuten. Gibt es eine andere Möglichkeit als die Verwendung von SP, um die Leistung zu verbessern. Das ist mein Code: 

 foreach (Employees item in sequence)
 {
   t = new Employees ();
   t.Text = item.Text;
   dataContext.Employees.AddObject(t);                  
 }
 dataContext.SaveChanges();
105
Vahid Ghadiri

Es gibt Möglichkeiten für mehrere Verbesserungen (wenn Sie DbContext verwenden):

Einstellen:

yourContext.Configuration.AutoDetectChangesEnabled = false;
yourContext.Configuration.ValidateOnSaveEnabled = false;

Do SaveChanges() in Paketen mit 100 Einsätzen ... oder Sie können es mit Paketen von 1000 Elementen versuchen und die Leistungsänderungen sehen. 

Da bei all diesen Einfügungen der Kontext derselbe ist und größer wird, können Sie Ihr Kontextobjekt alle 1000 Einfügungen neu erstellen.var yourContext = new YourContext(); Ich denke, das ist der große Gewinn.

Diese Verbesserungen beim Importieren von Daten von mir dauerten von 7 Minuten bis 6 Sekunden. 

Die tatsächlichen Zahlen ... könnten in Ihrem Fall nicht 100 oder 1000 sein ... Probieren Sie es aus und optimieren Sie es.

204
Romias

Es gibt keine Möglichkeit, EF zu zwingen, die Leistung auf diese Weise zu verbessern. Das Problem ist, dass EF jede Einfügung in einer separaten Rundfahrt zur Datenbank ausführt. Großartig, nicht wahr? Sogar DataSets unterstützten die Stapelverarbeitung. Überprüfen Sie diesen Artikel für eine Problemumgehung. Eine andere Problemumgehung kann die Verwendung einer benutzerdefinierten gespeicherten Prozedur sein, die einen Tabellenwertparameter akzeptiert, aber Sie benötigen dazu reines ADO.NET.

42
Ladislav Mrnka

Sie können bulk insert extension verwenden

Hier ist eine kleine Vergleichstabelle

EntityFramework.BulkInsert vs EF AddRange_ Code-Auszug _

hoffe das hilft

context.BulkInsert(hugeAmountOfEntities);

hope this helps

33
maxlego

Mit dem folgenden Code können Sie die partielle Kontextklasse mit einer Methode erweitern, mit der eine Sammlung von Entitätsobjekten in die Datenbank kopiert werden kann. Ersetzen Sie einfach den Namen der Klasse von MyEntities in den Namen Ihrer Entitätsklasse und fügen Sie ihn im richtigen Namespace zu Ihrem Projekt hinzu. Danach müssen Sie nur noch die BulkInsertAll-Methode aufrufen und die Entitätsobjekte übergeben, die Sie einfügen möchten. Verwenden Sie die Kontextklasse nicht erneut, sondern erstellen Sie bei jeder Verwendung eine neue Instanz. Dies ist zumindest in einigen Versionen von EF erforderlich, da die Authentifizierungsdaten, die der hier verwendeten SQLConnection zugeordnet sind, nach einmaliger Verwendung der Klasse verloren gehen. Ich weiß nicht warum.

Diese Version ist für EF 5

public partial class MyEntities
{
    public void BulkInsertAll<T>(T[] entities) where T : class
    {
        var conn = (SqlConnection)Database.Connection;

        conn.Open();

        Type t = typeof(T);
        Set(t).ToString();
        var objectContext = ((IObjectContextAdapter)this).ObjectContext;
        var workspace = objectContext.MetadataWorkspace;
        var mappings = GetMappings(workspace, objectContext.DefaultContainerName, typeof(T).Name);

        var tableName = GetTableName<T>();
        var bulkCopy = new SqlBulkCopy(conn) { DestinationTableName = tableName };

        // Foreign key relations show up as virtual declared 
        // properties and we want to ignore these.
        var properties = t.GetProperties().Where(p => !p.GetGetMethod().IsVirtual).ToArray();
        var table = new DataTable();
        foreach (var property in properties)
        {
            Type propertyType = property.PropertyType;

            // Nullable properties need special treatment.
            if (propertyType.IsGenericType &&
                propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                propertyType = Nullable.GetUnderlyingType(propertyType);
            }

            // Since we cannot trust the CLR type properties to be in the same order as
            // the table columns we use the SqlBulkCopy column mappings.
            table.Columns.Add(new DataColumn(property.Name, propertyType));
            var clrPropertyName = property.Name;
            var tableColumnName = mappings[property.Name];
            bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(clrPropertyName, tableColumnName));
        }

        // Add all our entities to our data table
        foreach (var entity in entities)
        {
            var e = entity;
            table.Rows.Add(properties.Select(property => GetPropertyValue(property.GetValue(e, null))).ToArray());
        }

        // send it to the server for bulk execution
        bulkCopy.BulkCopyTimeout = 5 * 60;
        bulkCopy.WriteToServer(table);

        conn.Close();
    }

    private string GetTableName<T>() where T : class
    {
        var dbSet = Set<T>();
        var sql = dbSet.ToString();
        var regex = new Regex(@"FROM (?<table>.*) AS");
        var match = regex.Match(sql);
        return match.Groups["table"].Value;
    }

    private object GetPropertyValue(object o)
    {
        if (o == null)
            return DBNull.Value;
        return o;
    }

    private Dictionary<string, string> GetMappings(MetadataWorkspace workspace, string containerName, string entityName)
    {
        var mappings = new Dictionary<string, string>();
        var storageMapping = workspace.GetItem<GlobalItem>(containerName, DataSpace.CSSpace);
        dynamic entitySetMaps = storageMapping.GetType().InvokeMember(
            "EntitySetMaps",
            BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance,
            null, storageMapping, null);

        foreach (var entitySetMap in entitySetMaps)
        {
            var typeMappings = GetArrayList("TypeMappings", entitySetMap);
            dynamic typeMapping = typeMappings[0];
            dynamic types = GetArrayList("Types", typeMapping);

            if (types[0].Name == entityName)
            {
                var fragments = GetArrayList("MappingFragments", typeMapping);
                var fragment = fragments[0];
                var properties = GetArrayList("AllProperties", fragment);
                foreach (var property in properties)
                {
                    var edmProperty = GetProperty("EdmProperty", property);
                    var columnProperty = GetProperty("ColumnProperty", property);
                    mappings.Add(edmProperty.Name, columnProperty.Name);
                }
            }
        }

        return mappings;
    }

    private ArrayList GetArrayList(string property, object instance)
    {
        var type = instance.GetType();
        var objects = (IEnumerable)type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance, null, instance, null);
        var list = new ArrayList();
        foreach (var o in objects)
        {
            list.Add(o);
        }
        return list;
    }

    private dynamic GetProperty(string property, object instance)
    {
        var type = instance.GetType();
        return type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance, null, instance, null);
    }
}

Diese Version ist für EF 6

public partial class CMLocalEntities
{
    public void BulkInsertAll<T>(T[] entities) where T : class
    {
        var conn = (SqlConnection)Database.Connection;

        conn.Open();

        Type t = typeof(T);
        Set(t).ToString();
        var objectContext = ((IObjectContextAdapter)this).ObjectContext;
        var workspace = objectContext.MetadataWorkspace;
        var mappings = GetMappings(workspace, objectContext.DefaultContainerName, typeof(T).Name);

        var tableName = GetTableName<T>();
        var bulkCopy = new SqlBulkCopy(conn) { DestinationTableName = tableName };

        // Foreign key relations show up as virtual declared 
        // properties and we want to ignore these.
        var properties = t.GetProperties().Where(p => !p.GetGetMethod().IsVirtual).ToArray();
        var table = new DataTable();
        foreach (var property in properties)
        {
            Type propertyType = property.PropertyType;

            // Nullable properties need special treatment.
            if (propertyType.IsGenericType &&
                propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                propertyType = Nullable.GetUnderlyingType(propertyType);
            }

            // Since we cannot trust the CLR type properties to be in the same order as
            // the table columns we use the SqlBulkCopy column mappings.
            table.Columns.Add(new DataColumn(property.Name, propertyType));
            var clrPropertyName = property.Name;
            var tableColumnName = mappings[property.Name];
            bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(clrPropertyName, tableColumnName));
        }

        // Add all our entities to our data table
        foreach (var entity in entities)
        {
            var e = entity;
            table.Rows.Add(properties.Select(property => GetPropertyValue(property.GetValue(e, null))).ToArray());
        }

        // send it to the server for bulk execution
        bulkCopy.BulkCopyTimeout = 5*60;
        bulkCopy.WriteToServer(table);

        conn.Close();
    }

    private string GetTableName<T>() where T : class
    {
        var dbSet = Set<T>();
        var sql = dbSet.ToString();
        var regex = new Regex(@"FROM (?<table>.*) AS");
        var match = regex.Match(sql);
        return match.Groups["table"].Value;
    }

    private object GetPropertyValue(object o)
    {
        if (o == null)
            return DBNull.Value;
        return o;
    }

    private Dictionary<string, string> GetMappings(MetadataWorkspace workspace, string containerName, string entityName)
    {
        var mappings = new Dictionary<string, string>();
        var storageMapping = workspace.GetItem<GlobalItem>(containerName, DataSpace.CSSpace);
        dynamic entitySetMaps = storageMapping.GetType().InvokeMember(
            "EntitySetMaps",
            BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance,
            null, storageMapping, null);

        foreach (var entitySetMap in entitySetMaps)
        {
            var typeMappings = GetArrayList("EntityTypeMappings", entitySetMap);
            dynamic typeMapping = typeMappings[0];
            dynamic types = GetArrayList("Types", typeMapping);

            if (types[0].Name == entityName)
            {
                var fragments = GetArrayList("MappingFragments", typeMapping);
                var fragment = fragments[0];
                var properties = GetArrayList("AllProperties", fragment);
                foreach (var property in properties)
                {
                    var edmProperty = GetProperty("EdmProperty", property);
                    var columnProperty = GetProperty("ColumnProperty", property);
                    mappings.Add(edmProperty.Name, columnProperty.Name);
                }
            }
        }

        return mappings;
    }

    private ArrayList GetArrayList(string property, object instance)
    {
        var type = instance.GetType();
        var objects = (IEnumerable)type.InvokeMember(
            property, 
            BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, instance, null);
        var list = new ArrayList();
        foreach (var o in objects)
        {
            list.Add(o);
        }
        return list;
    }

    private dynamic GetProperty(string property, object instance)
    {
        var type = instance.GetType();
        return type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, instance, null);
    }

}

Und zum Schluss noch ein bisschen was für Linq-To-Sql-Liebhaber.

partial class MyDataContext
{
    partial void OnCreated()
    {
        CommandTimeout = 5 * 60;
    }

    public void BulkInsertAll<T>(IEnumerable<T> entities)
    {
        entities = entities.ToArray();

        string cs = Connection.ConnectionString;
        var conn = new SqlConnection(cs);
        conn.Open();

        Type t = typeof(T);

        var tableAttribute = (TableAttribute)t.GetCustomAttributes(
            typeof(TableAttribute), false).Single();
        var bulkCopy = new SqlBulkCopy(conn) { 
            DestinationTableName = tableAttribute.Name };

        var properties = t.GetProperties().Where(EventTypeFilter).ToArray();
        var table = new DataTable();

        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 => GetPropertyValue(
              property.GetValue(entity, null))).ToArray());
        }

        bulkCopy.WriteToServer(table);
        conn.Close();
    }

    private bool EventTypeFilter(System.Reflection.PropertyInfo p)
    {
        var attribute = Attribute.GetCustomAttribute(p, 
            typeof (AssociationAttribute)) as AssociationAttribute;

        if (attribute == null) return true;
        if (attribute.IsForeignKey == false) return true; 

        return false;
    }

    private object GetPropertyValue(object o)
    {
        if (o == null)
            return DBNull.Value;
        return o;
    }
}
25
Måns Tånneryd

Vielleicht hilft diese Antwort hier. Scheint, dass Sie den Kontext regelmäßig entsorgen möchten. Dies liegt daran, dass der Kontext mit dem Anhängen der angefügten Entitäten immer größer wird. 

8
MemeDeveloper

Besser ist es, das Entity Framework für diesen Vorgang vollständig zu überspringen und sich auf die SqlBulkCopy-Klasse zu verlassen. Andere Operationen können EF weiterhin wie zuvor verwenden.

Dies erhöht die Wartungskosten der Lösung, trägt jedoch dazu bei, die Zeit, die zum Einfügen großer Sammlungen von Objekten in die Datenbank erforderlich ist, um ein bis zwei Größenordnungen im Vergleich zur Verwendung von EF zu reduzieren.

Hier ist ein Artikel, der die SqlBulkCopy-Klasse mit EF für Objekte mit Parent-Child-Beziehung vergleicht (beschreibt auch Änderungen im Entwurf, die zur Implementierung des Masseneinfügens erforderlich sind): Masseneinfügung komplexer Objekte in die SQL Server-Datenbank

4
Zoran Horvat

In einer Azure-Umgebung mit einer Basic-Website, die 1 Instance hat. Ich habe versucht, einen Stapel von 1000 Datensätzen gleichzeitig mit 25000 Datensätzen für for loop einzufügen. Es dauerte 11,5 Minuten, aber in paralleler Ausführung dauerte es weniger als eine Minute (Task Parallel Library).

         var count = (you collection / 1000) + 1;
         Parallel.For(0, count, x =>
        {
            ApplicationDbContext db1 = new ApplicationDbContext();
            db1.Configuration.AutoDetectChangesEnabled = false;

            var records = members.Skip(x * 1000).Take(1000).ToList();
            db1.Members.AddRange(records).AsParallel();

            db1.SaveChanges();
            db1.Dispose();
        });
4
user3571683

Momentan gibt es keinen besseren Weg, es kann jedoch eine geringfügige Verbesserung geben, indem SaveChanges für wahrscheinlich 10 Elemente in der Schleife verschoben wird.

int i = 0;

foreach (Employees item in sequence)
{
   t = new Employees ();
   t.Text = item.Text;
   dataContext.Employees.AddObject(t);   

   // this will add max 10 items together
   if((i % 10) == 0){
       dataContext.SaveChanges();
       // show some progress to user based on
       // value of i
   }
   i++;
}
dataContext.SaveChanges();

Sie können 10 einstellen, um der besseren Leistung näher zu kommen. Es wird die Geschwindigkeit nicht wesentlich verbessern, aber es wird Ihnen ermöglichen, dem Benutzer einige Fortschritte zu zeigen und es benutzerfreundlicher zu machen.

4
Akash Kava

Versuchen Sie es mit Bulk Insert ....

http://code.msdn.Microsoft.com/LinqEntityDataReader

Wenn Sie über eine Sammlung von Entitäten verfügen, z. B. storeEntities, können Sie diese mit SqlBulkCopy wie folgt speichern

        var bulkCopy = new SqlBulkCopy(connection);
        bulkCopy.DestinationTableName = TableName;
        var dataReader = storeEntities.AsDataReader();
        bulkCopy.WriteToServer(dataReader);

Es gibt ein Problem mit diesem Code. Stellen Sie sicher, dass die Entity Framework-Definition für die Entität genau mit der Tabellendefinition korreliert. Stellen Sie sicher, dass die Entity-Eigenschaften im Entity-Modell in derselben Reihenfolge wie die Spalten in der SQL Server-Tabelle stehen. Andernfalls führt dies zu einer Ausnahme.

3
Mick

Es gibt zwei Hauptleistungsprobleme mit Ihrem Code:

  • Add-Methode verwenden
  • SaveChanges verwenden

Verwenden der Add-Methode

Die Add-Methode wird bei jeder hinzugefügten Entität immer langsamer.

Siehe: http://entityframework.net/improve-ef-add-performance

Zum Beispiel können Sie 10.000 Entitäten hinzufügen über:

  • Hinzufügen (~ 105.000ms nehmen)
  • AddRange (~ 120ms nehmen)

Hinweis: Entitäten wurden noch nicht in der Datenbank gespeichert!

Das Problem ist, dass die Add-Methode bei jeder hinzugefügten Entität versucht, DetectChanges zu ermitteln, während AddRange dies einmal ausführt, nachdem alle Entitäten dem Kontext hinzugefügt wurden.

Gemeinsame Lösungen sind:

  • Verwenden Sie AddRange über Add
  • Legen Sie AutoDetectChanges auf false fest
  • SPLIT SaveChanges in mehreren Batches

SaveChanges verwenden

Entity Framework wurde nicht für Massenvorgänge erstellt. Für jede Entität, die Sie speichern, wird eine Datenbankrundfahrt durchgeführt. 

Wenn Sie also 20.000 Datensätze einfügen möchten, führen Sie 20.000 Datenbankrundfahrten durch, die VERR&UUML;CKT sind!

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

EDIT: Frage in Kommentar beantworten

Gibt es eine empfohlene Maximalgröße für jede Masseneinfügung für die von Ihnen erstellte Bibliothek

Nicht zu hoch, nicht zu niedrig. Es gibt keinen bestimmten Wert, der in alle Szenarien passt, da er von mehreren Faktoren wie Zeilengröße, Index, Trigger usw. abhängt.

Es wird normalerweise empfohlen, um 4000 zu sein.

Gibt es auch eine Möglichkeit, alles in einer Transaktion zu binden und sich keine Sorgen darüber zu machen, dass das Zeitlimit überschritten wird?

Sie können die Entity Framework-Transaktion verwenden. Unsere Bibliothek verwendet die Transaktion, wenn eine gestartet wird. Aber Vorsicht, eine Transaktion, die zu viel Zeit in Anspruch nimmt, kann auch Probleme mit sich bringen, wie beispielsweise eine Zeilen-, Index- oder Tabellensperre.

3
Jonathan Magnan

Zwar eine späte Antwort, aber ich poste die Antwort, weil ich die gleichen Schmerzen hatte ... Ich habe ein neues GitHub-Projekt dafür erstellt. Ab sofort unterstützt es Bulk Insert/Update/Delete für SQL-Server transparent using SqlBulkCopy.

https://github.com/MHanafy/EntityExtensions

Es gibt auch andere Leckereien, und hoffentlich wird es erweitert, um auf der Strecke noch mehr zu tun.

Die Verwendung ist so einfach wie

var insertsAndupdates = new List<object>();
var deletes = new List<object>();
context.BulkUpdate(insertsAndupdates, deletes);

Ich hoffe es hilft!

0
Mahmoud Hanafy