Question Le moyen le plus rapide d'insertion dans Entity Framework
Je suis à la recherche du moyen le plus rapide d'insérer dans Entity Framework.
Je demande cela à cause du scénario où vous avez un TransactionScope actif et l'insertion est énorme (4000+). Il peut potentiellement durer plus de 10 minutes (délai d'expiration par défaut des transactions), ce qui entraînera une transaction incomplète.
543
2018-05-09 17:14
origine
Réponses:
À votre remarque dans les commentaires à votre question:
"...Enregistrer les modifications (pour chaque
record) ... "
C'est la pire chose que tu puisses faire! Appel SaveChanges()
pour chaque enregistrement ralentit insère en vrac extrêmement bas. Je ferais quelques tests simples qui vont très probablement améliorer les performances:
- Appel
SaveChanges()
une fois après TOUS les enregistrements.
- Appel
SaveChanges()
après par exemple 100 enregistrements.
- Appel
SaveChanges()
après par exemple 100 enregistrements et disposer du contexte et en créer un nouveau.
- Désactiver la détection de changement
Pour les encarts en vrac, je travaille et expérimente avec un motif comme celui-ci:
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;
}
J'ai un programme de test qui insère 560.000 entités (9 propriétés scalaires, pas de propriétés de navigation) dans la base de données. Avec ce code, il fonctionne en moins de 3 minutes.
Pour la performance, il est important d'appeler SaveChanges()
après "plusieurs" enregistrements ("beaucoup" autour de 100 ou 1000). Il améliore également les performances pour disposer du contexte après SaveChanges et en créer un nouveau. Cela efface le contexte de tous les entites, SaveChanges
ne fait pas cela, les entités sont toujours attachées au contexte dans l'état Unchanged
. C'est la taille croissante des entités attachées dans le contexte qui ralentit l'insertion étape par étape. Donc, il est utile de l'effacer après un certain temps.
Voici quelques mesures pour mes 560 000 entités:
- commitCount = 1, recreateContext = false: beaucoup d'heures (C'est votre procédure actuelle)
- commitCount = 100, recreateContext = false: plus de 20 minutes
- commitCount = 1000, recreateContext = false: 242 sec
- commitCount = 10000, recreateContext = false: 202 sec
- commitCount = 100000, recreateContext = false: 199 sec
- commitCount = 1000000, recreateContext = false: Exception de mémoire insuffisante
- commitCount = 1, recreateContext = true: plus de 10 minutes
- commitCount = 10, recreateContext = true: 241 sec
- commitCount = 100, recreateContext = true: 164 sec
- commitCount = 1000, recreateContext = true: 191 sec
Le comportement dans le premier test ci-dessus est que la performance est très non linéaire et diminue extrêmement au fil du temps. ("Beaucoup d'heures" est une estimation, je n'ai jamais fini ce test, je me suis arrêté à 50.000 entités après 20 minutes.) Ce comportement non linéaire n'est pas si important dans tous les autres tests.
841
2018-05-09 20:33
Cette combinaison augmente la vitesse assez bien.
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
162
2017-08-12 15:52
Le moyen le plus rapide serait d'utiliser extension d'insert en vrac, que j'ai développé.
Il utilise SqlBulkCopy et le lecteur de données personnalisé pour obtenir des performances maximales. En conséquence, il est plus de 20 fois plus rapide que l'insertion régulière ou AddRange

l'utilisation est extrêmement simple
context.BulkInsert(hugeAmountOfEntities);
96
2018-02-17 21:18
Vous devriez regarder en utilisant le System.Data.SqlClient.SqlBulkCopy
pour ça. Ici se trouve le Documentation, et bien sûr il y a beaucoup de tutoriels en ligne.
Désolé, je sais que vous cherchiez une réponse simple pour qu'EF fasse ce que vous voulez, mais les opérations en vrac ne sont pas vraiment ce que les ORM sont destinés.
66
2018-05-09 17:17
Je suis d'accord avec Adam Rackis. SqlBulkCopy
est le moyen le plus rapide de transférer des enregistrements en masse d'une source de données à une autre. J'ai utilisé ceci pour copier des enregistrements 20K et cela a pris moins de 3 secondes. Jetez un oeil à l'exemple ci-dessous.
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();
}
}
}
46
2018-01-15 10:27
J'ai étudié la réponse de Slauma (ce qui est génial, merci pour l'idée de l'homme), et j'ai réduit la taille du lot jusqu'à ce que j'aie atteint la vitesse optimale. En regardant les résultats du Slauma:
- commitCount = 1, recreateContext = true: plus de 10 minutes
- commitCount = 10, recreateContext = true: 241 sec
- commitCount = 100, recreateContext = true: 164 sec
- commitCount = 1000, recréerContext = true: 191 sec
Il est visible qu'il y a une augmentation de la vitesse en passant de 1 à 10, et de 10 à 100, mais de 100 à 1000, la vitesse d'insertion redescend.
Je me suis donc concentré sur ce qui se passe lorsque vous réduisez la taille d'un lot à une valeur comprise entre 10 et 100, et voici mes résultats (j'utilise un contenu de ligne différent, mes temps ont donc une valeur différente):
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
Sur la base de mes résultats, l'optimum réel est de l'ordre de 30 pour la taille du lot. C'est moins que les deux 10 et 100. Le problème est, je ne sais pas pourquoi 30 est optimal, et je n'ai pu trouver aucune explication logique pour cela.
18
2018-03-26 08:46
Je recommanderais cet article sur comment faire des insertions en vrac en utilisant EF.
Entity Framework et INSERT en masse lente
Il explore ces domaines et compare la performance:
- EF par défaut (57 minutes pour compléter l'ajout de 30 000 enregistrements)
- Remplacement par le code ADO.NET (25 secondes pour ces mêmes 30 000)
- Context Bloat - Gardez le graphique de contexte actif petit en utilisant un nouveau contexte pour chaque unité de travail (même 30 000 insertions prennent 33 secondes)
- Grandes listes - Désactivez AutoDetectChangesEnabled (réduit le temps à environ 20 secondes)
- Lot (jusqu'à 16 secondes)
- DbTable.AddRange () - (la performance est dans la gamme 12)
16
2018-06-20 03:10
Comme d'autres personnes l'ont dit, SqlBulkCopy est le moyen de le faire si vous voulez vraiment de bonnes performances d'insertion.
C'est un peu lourd à mettre en place, mais il y a des bibliothèques qui peuvent vous aider. Il y en a quelques-uns mais je vais sans scrupule brancher ma propre bibliothèque cette fois-ci: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities
Le seul code dont vous auriez besoin est:
using (var db = new YourDbContext())
{
EFBatchOperation.For(db, db.BlogPosts).InsertAll(list);
}
Alors, combien plus vite est-ce? Très difficile à dire car cela dépend de nombreux facteurs, des performances de l'ordinateur, du réseau, de la taille de l'objet, etc. Les tests de performance que j'ai effectués suggèrent que 25k entités peuvent être insérées à environ 10s le manière standard sur localhost SI vous optimisez votre configuration EF comme mentionné dans les autres réponses. Avec EFUtilities qui prend environ 300ms. Encore plus intéressant est que j'ai économisé environ 3 millions d'entités en moins de 15 secondes en utilisant cette méthode, avec une moyenne d'environ 200 000 entités par seconde.
Le problème est bien sûr si vous avez besoin d'insérer des données releated. Cela peut être fait efficacement dans le serveur SQL en utilisant la méthode ci-dessus, mais cela nécessite que vous ayez une stratégie de génération d'ID qui vous permet de générer des ID dans le code de l'application pour le parent afin de pouvoir définir les clés étrangères. Cela peut être fait en utilisant des GUID ou quelque chose comme la génération d'ID HiLo.
14
2017-12-19 10:44