Amélioration des performances des insertions en masse dans le framework Entity

Je veux insérer 20000 enregistrements dans un tableau par entité et cela prend environ 2 minutes. Y a-t-il un autre moyen que d’utiliser SP pour améliorer ses performances. Ceci est mon code:

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

Il y a des possibilités pour plusieurs améliorations (si vous utilisez DbContext ):

Ensemble:

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

Do SaveChanges() dans des packages de 100 insertions … ou vous pouvez essayer avec des packages de 1000 éléments et voir les changements de performances.

Étant donné que pendant tout ce processus, le contexte est le même et s’agrandit, vous pouvez reconstruire votre object contextuel toutes les 1000 insertions. var yourContext = new YourContext(); Je pense que c’est le gros gain.

Faire ces améliorations dans un processus d’importation de données a pris de 7 minutes à 6 secondes.

Les nombres réels … ne pourraient pas être 100 ou 1000 dans votre cas … essayez-le et ajustez-le.

Il n’y a aucun moyen de forcer EF à améliorer les performances lorsque vous le faites de cette manière. Le problème est que EF exécute chaque insertion dans un aller-retour séparé vers la firebase database. Génial n’est ce pas? Même DataSets pris en charge le traitement par lots. Consultez cet article pour une solution de contournement. Une autre solution de contournement peut être l’utilisation d’un paramètre de valeur de table d’acceptation de procédure stockée personnalisée, mais vous avez besoin de ADO.NET brut pour cela.

Vous pouvez utiliser l’ extension d’insertion en vrac

Voici un petit tableau de comparaison

EntityFramework.BulkInsert vs EF AddRange

et l’utilisation est extrêmement simple

 context.BulkInsert(hugeAmountOfEntities); 

J’espère que cela t’aides

En utilisant le code ci-dessous, vous pouvez étendre la classe de contexte partiel avec une méthode qui prendra une collection d’objects d’entité et les copiera en bloc dans la firebase database. Remplacez simplement le nom de la classe de MyEntities par le nom de votre classe d’entité et ajoutez-le à votre projet, dans le bon espace de noms. Après cela, il vous suffit d’appeler la méthode BulkInsertAll pour transférer les objects d’entité à insérer. Ne réutilisez pas la classe de contexte, mais créez une nouvelle instance chaque fois que vous l’utilisez. Cela est nécessaire, au moins dans certaines versions d’EF, car les données d’authentification associées à la SQLConnection utilisée ici sont perdues après avoir utilisé la classe une fois. Je ne sais pas pourquoi.

Cette version est pour EF 5

 public partial class MyEntities { public void BulkInsertAll(T[] entities) where T : class { var conn = (SqlConnection)Database.Connection; conn.Open(); Type t = typeof(T); Set(t).ToSsortingng(); var objectContext = ((IObjectContextAdapter)this).ObjectContext; var workspace = objectContext.MetadataWorkspace; var mappings = GetMappings(workspace, objectContext.DefaultContainerName, typeof(T).Name); var tableName = GetTableName(); 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 ssortingng GetTableName() where T : class { var dbSet = Set(); var sql = dbSet.ToSsortingng(); var regex = new Regex(@"FROM (?.*) 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 GetMappings(MetadataWorkspace workspace, ssortingng containerName, ssortingng entityName) { var mappings = new Dictionary(); var storageMapping = workspace.GetItem(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(ssortingng 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(ssortingng property, object instance) { var type = instance.GetType(); return type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance, null, instance, null); } }

Cette version est pour EF 6

 public partial class CMLocalEntities { public void BulkInsertAll(T[] entities) where T : class { var conn = (SqlConnection)Database.Connection; conn.Open(); Type t = typeof(T); Set(t).ToSsortingng(); var objectContext = ((IObjectContextAdapter)this).ObjectContext; var workspace = objectContext.MetadataWorkspace; var mappings = GetMappings(workspace, objectContext.DefaultContainerName, typeof(T).Name); var tableName = GetTableName(); 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 ssortingng GetTableName() where T : class { var dbSet = Set(); var sql = dbSet.ToSsortingng(); var regex = new Regex(@"FROM (?.*) 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 GetMappings(MetadataWorkspace workspace, ssortingng containerName, ssortingng entityName) { var mappings = new Dictionary(); var storageMapping = workspace.GetItem(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(ssortingng 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(ssortingng property, object instance) { var type = instance.GetType(); return type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, instance, null); } }

Et enfin, un petit quelque chose pour vous les amoureux de Linq-To-Sql.

 partial class MyDataContext { partial void OnCreated() { CommandTimeout = 5 * 60; } public void BulkInsertAll(IEnumerable entities) { entities = entities.ToArray(); ssortingng cs = Connection.ConnectionSsortingng; var conn = new SqlConnection(cs); conn.Open(); Type t = typeof(T); var tableAtsortingbute = (TableAtsortingbute)t.GetCustomAtsortingbutes( typeof(TableAtsortingbute), false).Single(); var bulkCopy = new SqlBulkCopy(conn) { DestinationTableName = tableAtsortingbute.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 atsortingbute = Atsortingbute.GetCustomAtsortingbute(p, typeof (AssociationAtsortingbute)) as AssociationAtsortingbute; if (atsortingbute == null) return true; if (atsortingbute.IsForeignKey == false) return true; return false; } private object GetPropertyValue(object o) { if (o == null) return DBNull.Value; return o; } } 

Peut-être que cette réponse ici vous aidera. Il semblerait que vous vouliez disposer du contexte périodiquement. C’est parce que le contexte devient de plus en plus grand à mesure que les entités attachées se développent.

Actuellement, il n’y a pas de meilleur moyen, cependant il peut y avoir une amélioration marginale en déplaçant SaveChanges à l’intérieur de la boucle pour probablement 10 éléments.

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

Vous pouvez ajuster 10 pour être plus proche de meilleures performances. Cela n’améliorera pas beaucoup la vitesse mais cela vous permettra de montrer des progrès à l’utilisateur et de le rendre plus convivial.

Dans l’environnement Azure avec le site Web de base qui a 1 instance.J’ai essayé d’insérer un lot de 1000 enregistrements à la fois sur 25000 enregistrements utilisant pour la boucle, il m’a fallu 11,5 minutes, mais en parallèle, cela m’a pris moins d’une minute. (Bibliothèque parallèle de tâches).

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

Le meilleur moyen consiste à ignorer entièrement Entity Framework pour cette opération et à utiliser la classe SqlBulkCopy. D’autres opérations peuvent continuer à utiliser EF comme auparavant.

Cela augmente le coût de maintenance de la solution, mais permet de réduire le temps nécessaire pour insérer de grandes quantités d’objects dans la firebase database d’un à deux ordres de grandeur par rapport à l’utilisation d’EF.

Voici un article qui compare la classe SqlBulkCopy avec EF pour les objects avec une relation parent-enfant (décrit également les modifications de conception requirejses pour implémenter l’insertion en bloc): Procédure d’ insertion en bloc d’ objects complexes dans la firebase database SQL Server

Essayez d’utiliser l’insertion en bloc ….

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

Si vous avez une collection d’entités, par exemple storeEntities, vous pouvez les stocker en utilisant SqlBulkCopy comme suit

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

Il y en a un avec ce code. Assurez-vous que la définition de l’entité Framework pour l’entité correspond exactement à la définition de la table, vérifiez que les propriétés de l’entité sont dans le même ordre dans le modèle d’entité que les colonnes de la table SQL Server. Ne pas le faire entraînera une exception.

Votre code présente deux problèmes de performance majeurs:

  • Utiliser la méthode Add
  • Utilisation de SaveChanges

Utiliser la méthode Add

La méthode Add devient plus lente et plus lente pour chaque entité ajoutée.

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

Par exemple, append 10 000 entités via:

  • Ajouter (prendre ~ 105,000ms)
  • AddRange (prendre ~ 120ms)

Remarque: les entités n’ont pas encore été enregistrées dans la firebase database!

Le problème est que la méthode Add tente de détecter les modifications à chaque entité ajoutée alors que AddRange le fait une fois après que toutes les entités ont été ajoutées au contexte.

Les solutions communes sont:

  • Utilisez AddRange over Add
  • SET AutoDetectChanges sur false
  • SPLIT SaveChanges dans plusieurs lots

Utilisation de SaveChanges

Entity Framework n’a pas été créé pour les opérations en bloc. Pour chaque entité que vous enregistrez, un aller-retour de firebase database est effectué.

Donc, si vous souhaitez insérer 20 000 enregistrements, vous effectuerez 20 000 allers-retours avec la firebase database INSANE !

Certaines bibliothèques tierces prenant en charge l’insertion en bloc sont disponibles:

  • Z.EntityFramework.Extensions ( Recommandé )
  • EFUtilities
  • EntityFramework.BulkInsert

Voir: Bibliothèque Entity Framework Bulk Insert

Soyez prudent lorsque vous choisissez une bibliothèque d’insertion en bloc. Seules les extensions Entity Framework prennent en charge tous les types d’associations et d’inheritance, et c’est le seul encore pris en charge.


Clause de nonresponsabilité : Je suis le propriétaire d’ Entity Framework Extensions

Cette bibliothèque vous permet d’effectuer toutes les opérations en masse dont vous avez besoin pour vos scénarios:

  • Modifications en vrac
  • Insert en vrac
  • Supprimer en bloc
  • Mise à jour en vrac
  • Fusion en vrac

Exemple

 // 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: Répondre à une question dans un commentaire

Existe-t-il une taille maximale recommandée pour chaque insertion en bloc pour la bibliothèque que vous avez créée?

Pas trop haut, pas trop bas. Aucune valeur particulière ne convient à tous les scénarios, car elle dépend de plusieurs facteurs tels que la taille de la ligne, l’index, le déclencheur, etc.

Il est normalement recommandé d’être autour de 4000.

En outre, existe-t-il un moyen de tout regrouper en une seule transaction sans se soucier du délai

Vous pouvez utiliser la transaction Entity Framework. Notre bibliothèque utilise la transaction si une est démarrée. Mais attention, une transaction qui prend trop de temps entraîne également des problèmes tels que le locking des lignes / index / tables.

Bien qu’une réponse tardive, mais je poste la réponse parce que j’ai souffert de la même douleur. J’ai créé un nouveau projet GitHub uniquement pour cela. Il prend en charge, à partir de maintenant, l’insertion / mise à jour / suppression en bloc pour le serveur SQL en utilisant SqlBulkCopy.

https://github.com/MHanafy/EntityExtensions

Il y a aussi d’autres bonnes choses, et j’espère que cela sera étendu pour faire plus de choses.

L’utiliser est aussi simple que

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

J’espère que cela aide!

  Use : db.Set.AddRange(list); Ref : TESTEntities db = new TESTEntities(); List persons = new List { new Person{Name="p1",Place="palce"}, new Person{Name="p2",Place="palce"}, new Person{Name="p3",Place="palce"}, new Person{Name="p4",Place="palce"}, new Person{Name="p5",Place="palce"} }; db.Set().AddRange(persons); db.SaveChanges();