Je cherche le moyen le plus rapide d'insérer dans Entity Framework.
Je pose cette question en raison du scénario dans lequel vous avez un TransactionScope actif et l'insertion est énorme (4000+). Cela peut potentiellement durer plus de 10 minutes (délai d'expiration par défaut des transactions), ce qui entraînera une transaction incomplète.
c#
sql
entity-framework
Bongo Sharp
la source
la source
Réponses:
À votre remarque dans les commentaires à votre question:
C'est la pire chose que vous puissiez faire! L'appel
SaveChanges()
pour chaque enregistrement ralentit considérablement les insertions en masse. Je ferais quelques tests simples qui amélioreront très probablement les performances:SaveChanges()
une fois après TOUS les enregistrements.SaveChanges()
par exemple 100 enregistrements.SaveChanges()
par exemple 100 enregistrements et supprimez le contexte et créez-en un nouveau.Pour les insertions en masse, je travaille et expérimente avec un modèle comme celui-ci:
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 les performances, il est important d'appeler
SaveChanges()
après "plusieurs" enregistrements ("plusieurs" autour de 100 ou 1000). Il améliore également les performances pour éliminer le contexte après SaveChanges et en créer un nouveau. Cela efface le contexte de toutes les entités,SaveChanges
ne fait pas cela, les entités sont toujours attachées au contexte dans l'étatUnchanged
. C'est la taille croissante des entités attachées dans le contexte qui ralentit l'insertion pas à pas. Il est donc utile de l'effacer après un certain temps.Voici quelques mesures pour mes 560000 entités:
Le comportement dans le premier test ci-dessus est que les performances sont très non linéaires et diminuent extrêmement au fil du temps. ("Plusieurs heures" est une estimation, je n'ai jamais terminé ce test, je me suis arrêté à 50 000 entités après 20 minutes.) Ce comportement non linéaire n'est pas si significatif dans tous les autres tests.
la source
AutoDetectChangesEnabled = false;
sur le DbContext. Il a également un gros effet supplémentaire sur les performances: stackoverflow.com/questions/5943394/…DbContext
NONObjectContext
?Cette combinaison augmente assez bien la vitesse.
la source
Le moyen le plus rapide serait d'utiliser l' extension d'insertion en vrac , que j'ai développée
note: ceci est un produit commercial, non gratuit
Il utilise SqlBulkCopy et un datareader personnalisé pour obtenir des performances maximales. En conséquence, il est plus de 20 fois plus rapide que l'utilisation de l'insertion régulière ou AddRange
l'utilisation est extrêmement simple
la source
Vous devriez envisager d'utiliser le
System.Data.SqlClient.SqlBulkCopy
pour cela. Voici la documentation , et bien sûr, il existe de nombreux tutoriels en ligne.Désolé, je sais que vous cherchiez une réponse simple pour que EF fasse ce que vous voulez, mais les opérations en bloc ne sont pas vraiment ce à quoi les ORM sont destinés.
la source
Je suis d'accord avec Adam Rackis.
SqlBulkCopy
est le moyen le plus rapide de transférer des enregistrements en vrac d'une source de données à une autre. Je l'ai utilisé pour copier 20 000 enregistrements et cela a pris moins de 3 secondes. Jetez un œil à l'exemple ci-dessous.la source
AsDataReader()
méthode d'extension, expliquée dans cette réponse: stackoverflow.com/a/36817205/1507899Je recommanderais cet article sur la façon de faire des insertions en masse à l'aide d'EF.
Entity Framework et INSERTS en vrac lents
Il explore ces domaines et compare les performances:
la source
comme cela n'a jamais été mentionné ici, je veux recommander EFCore.BulkExtensions ici
la source
J'ai enquêté sur la réponse de Slauma (ce qui est génial, merci pour l'idée homme), et j'ai réduit la taille des lots jusqu'à ce que j'atteigne la vitesse optimale. En regardant les résultats de Slauma:
Il est visible qu'il y a une augmentation de la vitesse lors du passage de 1 à 10 et de 10 à 100, mais de 100 à 1000, la vitesse d'insertion diminue à nouveau.
Je me suis donc concentré sur ce qui se passe lorsque vous réduisez la taille du lot à une valeur comprise entre 10 et 100, et voici mes résultats (j'utilise différents contenus de ligne, donc mes temps ont une valeur différente):
Sur la base de mes résultats, l'optimum réel est d'environ 30 pour la taille du lot. C'est moins que 10 et 100. Le problème, c'est que je n'ai aucune idée de pourquoi 30 est optimal, et je n'aurais pas pu trouver d'explication logique à cela.
la source
Comme d'autres l'ont dit, SqlBulkCopy est le moyen de le faire si vous voulez de très bonnes performances d'insertion.
C'est un peu lourd à mettre en œuvre mais il existe des bibliothèques qui peuvent vous aider. Il y en a quelques-uns mais je débrancherai sans vergogne ma propre bibliothèque cette fois: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities
Le seul code dont vous auriez besoin est:
Alors, c'est beaucoup plus rapide? Très difficile à dire parce que cela dépend de nombreux facteurs, la performance des ordinateurs, réseau, la taille des objets etc etc Les tests de performance que j'ai fait suggère 25k entités peuvent être insérées à environ 10s la manière standard sur localhost Si vous optimisez votre configuration EF comme mentionné dans les autres réponses. Avec EFUtilities, cela prend environ 300 ms. Encore plus intéressant, j'ai enregistré environ 3 millions d'entités en moins de 15 secondes en utilisant cette méthode, en moyenne environ 200 000 entités par seconde.
Le seul problème est bien sûr si vous devez insérer des données recréées. Cela peut être fait efficacement sur le serveur SQL à l'aide de la méthode ci-dessus, mais cela nécessite que vous ayez une stratégie de génération d'ID qui vous permette de générer des ID dans le code d'application pour le parent afin que vous puissiez 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.
la source
EFBatchOperation
avoir un constructeur que vous transmettezDbContext
à plutôt que de passer à chaque méthode statique. Des versions génériques deInsertAll
etUpdateAll
qui trouvent automatiquement la collection, similaires àDbContext.Set<T>
, seraient également bonnes.Dispose()
le contexte crée des problèmes si les entitésAdd()
sur lesquelles vous comptez sur d'autres entités préchargées (par exemple, les propriétés de navigation) dans le contexteJ'utilise un concept similaire pour garder mon contexte petit pour obtenir les mêmes performances
Mais au lieu du
Dispose()
contexte et recréer, je détache simplement les entités qui déjàSaveChanges()
envelopper avec try catch et
TrasactionScope()
si vous en avez besoin, ne pas les montrer ici pour garder le code proprela source
Je sais que c'est une très vieille question, mais un gars ici a dit avoir développé une méthode d'extension pour utiliser l'insertion en vrac avec EF, et quand j'ai vérifié, j'ai découvert que la bibliothèque coûte 599 $ aujourd'hui (pour un développeur). Peut-être que cela a du sens pour toute la bibliothèque, mais pour l'insertion en bloc, c'est trop.
Voici une méthode d'extension très simple que j'ai faite. Je l'utilise d'abord sur une paire avec une base de données (ne testez pas d'abord avec le code, mais je pense que cela fonctionne de la même manière). Changez
YourEntities
avec le nom de votre contexte:Vous pouvez l'utiliser contre n'importe quelle collection qui hérite de
IEnumerable
, comme ça:la source
await bulkCopy.WriteToServerAsync(table);
Essayez d'utiliser une procédure stockée qui obtiendra un XML des données que vous souhaitez insérer.
la source
J'ai fait une extension générique de l'exemple de @Slauma ci-dessus;
Usage:
la source
Certaines bibliothèques tierces prenant en charge l'insertion en bloc sont disponibles:
Voir: Bibliothèque d'insertion en bloc Entity Framework
Soyez prudent lorsque vous choisissez une bibliothèque d'insertion en bloc. Seules les extensions Entity Framework prennent en charge toutes sortes d'associations et d'héritages et c'est la seule encore prise en charge.
Avis de non - responsabilité : je suis le propriétaire des extensions d' entité Framework
Cette bibliothèque vous permet d'effectuer toutes les opérations en bloc dont vous avez besoin pour vos scénarios:
Exemple
la source
Utilisation
SqlBulkCopy
:la source
L'un des moyens les plus rapides pour enregistrer une liste, vous devez appliquer le code suivant
AutoDetectChangesEnabled = false
Add, AddRange & SaveChanges: ne détecte pas les changements.
ValidateOnSaveEnabled = false;
Ne détecte pas le suivi des modifications
Vous devez ajouter nuget
Vous pouvez maintenant utiliser le code suivant
la source
SqlBulkCopy est super rapide
Voici ma mise en œuvre:
la source
[Mise à jour 2019] EF Core 3.1
Suivant ce qui a été dit ci-dessus, la désactivation d'AutoDetectChangesEnabled dans EF Core a parfaitement fonctionné: le temps d'insertion a été divisé par 100 (de plusieurs minutes à quelques secondes, 10 000 enregistrements avec des relations de tables croisées)
Le code mis à jour est:
la source
Voici une comparaison des performances entre l'utilisation d'Entity Framework et l'utilisation de la classe SqlBulkCopy sur un exemple réaliste: Comment insérer en bloc des objets complexes dans la base de données SQL Server
Comme d'autres l'ont déjà souligné, les ORM ne sont pas destinés à être utilisés dans des opérations en vrac. Ils offrent flexibilité, séparation des préoccupations et autres avantages, mais les opérations en bloc (sauf la lecture en bloc) n'en font pas partie.
la source
Une autre option consiste à utiliser SqlBulkTools disponible auprès de Nuget. Il est très facile à utiliser et possède des fonctionnalités puissantes.
Exemple:
Voir la documentation pour plus d'exemples et une utilisation avancée. Avertissement: je suis l'auteur de cette bibliothèque et toutes les opinions sont de mon avis.
la source
Selon ma connaissance , il est
no BulkInsert
enEntityFramework
augmenter les performances des inserts énormes.Dans ce scénario , vous pouvez aller avec SqlBulkCopy dans
ADO.net
pour résoudre votre problèmela source
WriteToServer
qui prend unDataTable
.Avez-vous déjà essayé d'insérer un travail ou une tâche en arrière-plan?
Dans mon cas, j'insère 7760 registres, répartis dans 182 tables différentes avec des relations de clé étrangère (par NavigationProperties).
Sans la tâche, cela a pris 2 minutes et demie. Dans une tâche (
Task.Factory.StartNew(...)
), cela a pris 15 secondes.Je ne fais
SaveChanges()
qu'après avoir ajouté toutes les entités au contexte. (pour assurer l'intégrité des données)la source
Toutes les solutions écrites ici n'aident pas car lorsque vous effectuez SaveChanges (), les instructions d'insertion sont envoyées à la base de données une par une, c'est ainsi que fonctionne Entity.
Et si votre trajet vers la base de données et retour est de 50 ms par exemple, le temps nécessaire pour l'insertion est le nombre d'enregistrements x 50 ms.
Vous devez utiliser BulkInsert, voici le lien: https://efbulkinsert.codeplex.com/
J'ai obtenu un temps d'insertion réduit de 5-6 minutes à 10-12 secondes en l'utilisant.
la source
Vous pouvez utiliser la bibliothèque de packages en vrac . La version Bulk Insert 1.0.0 est utilisée dans les projets ayant un framework Entity> = 6.0.0.
Plus de description peut être trouvée ici - Code source de Bulkoperation
la source
[NOUVELLE SOLUTION POUR POSTGRESQL] Hé, je sais que c'est un article assez ancien, mais j'ai récemment rencontré un problème similaire, mais nous utilisions Postgresql. Je voulais utiliser un bulkinsert efficace, ce qui s'est avéré assez difficile. Je n'ai trouvé aucune bibliothèque gratuite appropriée pour le faire sur cette base de données. Je n'ai trouvé que cette aide: https://bytefish.de/blog/postgresql_bulk_insert/ qui est également sur Nuget. J'ai écrit un petit mappeur, qui mappe automatiquement les propriétés comme Entity Framework:
Je l'utilise de la manière suivante (j'avais une entité nommée Entreprise):
J'ai montré un exemple de transaction, mais cela peut aussi être fait avec une connexion normale récupérée du contexte. enterprisesToAdd est dénombrable des enregistrements d'entité normaux, que je veux insérer en masse dans DB.
Cette solution, à laquelle je suis arrivé après quelques heures de recherche et d'essais, est comme vous pouvez vous attendre beaucoup plus rapide et enfin facile à utiliser et gratuite! Je vous conseille vraiment d'utiliser cette solution, non seulement pour les raisons mentionnées ci-dessus, mais aussi parce que c'est la seule avec laquelle je n'ai eu aucun problème avec Postgresql lui-même, de nombreuses autres solutions fonctionnent parfaitement, par exemple avec SqlServer.
la source
Le secret est d'insérer dans une table de transfert vierge identique. Les inserts s'éclaircissent rapidement. Exécutez ensuite un seul insert à partir de cela dans votre grande table principale. Tronquez ensuite la table intermédiaire prête pour le prochain lot.
c'est à dire.
la source
Mais, pour plus de (+4000) insertions, je recommande d'utiliser la procédure stockée. joint le temps écoulé. Je l'ai inséré 11,788 lignes en 20 "
c'est le code
la source
Utilisez une procédure stockée qui prend les données d'entrée sous forme de XML pour insérer des données.
À partir de votre code c #, insérez des données au format xml.
par exemple en c #, la syntaxe serait comme ceci:
la source
Utilisez cette technique pour augmenter la vitesse d'insertion des enregistrements dans Entity Framework. Ici, j'utilise une procédure stockée simple pour insérer les enregistrements. Et pour exécuter cette procédure stockée, j'utilise la méthode .FromSql () d'Entity Framework qui exécute le SQL brut.
Le code de procédure stockée:
Ensuite, parcourez tous vos 4000 enregistrements et ajoutez le code Entity Framework qui exécute le stocké
la procédure se répète toutes les 100 boucles.
Pour cela, je crée une requête de chaîne pour exécuter cette procédure, continuez à lui ajouter tous les enregistrements.
Vérifiez ensuite que la boucle s'exécute par multiples de 100 et, dans ce cas, exécutez-la en utilisant
.FromSql()
.Vérifiez le code ci-dessous:
la source