2010-12-04 51 views
26

Je dois importer environ 30k lignes à partir d'un fichier CSV vers ma base de données SQL, cela prend malheureusement 20 minutes.Comment accélérer DbSet.Add()?

Dépannage avec un profileur me montre que DbSet.Add prend le plus de temps, mais pourquoi?

J'ai ces Entity Framework code-Premières classes:

public class Article 
{ 
    // About 20 properties, each property doesn't store excessive amounts of data 
} 

public class Database : DbContext 
{ 
    public DbSet<Article> Articles { get; set; } 
} 

Pour chaque élément dans ma boucle que je fais:

db.Articles.Add(article); 

En dehors de la boucle que je fais:

db.SaveChanges(); 

Il est connecté avec mon serveur SQLExpress local, mais je suppose qu'il n'y a rien écrit jusqu'à ce que Sav eChanges est appelé donc je suppose que le serveur ne sera pas le problème ....

+1

Bonjour. Vous êtes-vous débarrassé de Entity Framework ou avez-vous utilisé sqlbulkcopy avec EF? J'obtiens exactement le même problème avec .Add() –

+6

Si vous définissez ceux-ci: 'db.Configuration.ValidateOnSaveEnabled = false; db.Configuration.AutoDetectChangesEnabled = false; ' Il y a un énorme gain de performance. Vous devez être sûr de vos valeurs difficiles. –

+0

Utilisez les guillemets (') pour le code dans les commentaires. Regarde intéressant, je regarderai dans ces propriétés plus tard ... –

Répondre

8

Chaque élément dans une unité de travail a des frais généraux, car il doit vérifier (et mettre à jour) le gestionnaire d'identité, ajouter à divers La première chose que j'essaierais est de regrouper, disons, des groupes de 500 (changer ce nombre), en commençant par un nouveau (nouveau) contexte d'objet chaque fois - comme vous pouvez raisonnablement attendre performance télescopique. La rupture en lots empêche également une transaction mégalithique de tout arrêter.

Au-delà de cela; SqlBulkCopy. Il est conçu pour les grandes importations avec un minimum de frais généraux. Ce n'est pas EF cependant.

+0

+ 1 si applicable dans votre conception j'irais certainement avec SqlBulkCopy. –

+0

J'essaye d'accomplir quelque chose avec ceci maintenant, mais je me demande s'il acceptera simplement la correspondance basée sur les noms de colonne et pas sur ses propriétés ... –

+2

La suggestion de groupes l'a rendue un peu plus rapide mais elle n'a pas fonctionné vite assez. Après quelques itérations suite à des erreurs, SqlBulkCopy fonctionne, c'est du code méchant mais ça marche. Pourrait le refactoriser ou vérifier s'ils ont supporté l'insertion en masse plus tard ... Merci Marc et les gens sur le chat qui ont fait une suggestion similaire! Et regardez, quelque chose qui a pris ** 20 minutes ** prend maintenant ** 2 secondes **, c'est magique ... –

1

Je n'ai pas vraiment essayé cela, mais ma logique serait de conserver le pilote ODBC pour charger le fichier dans datatable, puis d'utiliser la procédure SQL stockée pour passer la table à la procédure.

Pour la première partie, essayez: http://www.c-sharpcorner.com/UploadFile/mahesh/AccessTextDb12052005071306AM/AccessTextDb.aspx

Pour la deuxième partie essayer la procédure SQL: http://www.builderau.com.au/program/sqlserver/soa/Passing-table-valued-parameters-in-SQL-Server-2008/0,339028455,339282577,00.htm

Et créer un objet SqlCommnand en C# et ajouter à sa collection de paramètres SqlParameter qui est SqlDbType. Structuré

Eh bien, j'espère que ça aide.

43

Selon le commentaire de Kevin Ramen (mars 29) Je peux confirmer que la mise en db.Configuration.AutoDetectChangesEnabled = false fait une énorme différence dans la vitesse

Courir Add() sur 2324 articles par défaut couru 15sec 3min sur ma machine, désactiver la détection automatique a permis dans l'opération complétant en 0.5sec.

http://blog.larud.net/archive/2011/07/12/bulk-load-items-to-a-ef-4-1-code-first-aspx

+0

Intéressant ... :) –

+0

C'est une chose génial à savoir !! Correction d'un énorme problème que j'avais inséré 4k enregistrements en utilisant EF sans refaire mon code pour utiliser la copie en bloc. Je pense que la copie en bloc est une réponse facile que les gens font sans analyser le problème plus loin. Dans mon cas, le sql-insert prenait <1s et l'ajout EF prenait 30-40 secondes donc cette solution de contournement fonctionne parfaitement. Merci pour l'information! – Alex

16

Je vais ajouter au commentaire de Kervin Ramen en disant que si vous ne faites que des inserts (aucune mise à jour ou de suppression), vous pouvez, en général, en toute sécurité définissez les propriétés suivantes avant de faire des inserts sur le contexte:

DbContext.Configuration.AutoDetectChangesEnabled = false; 
DbContext.Configuration.ValidateOnSaveEnabled = false; 

J'avais un problème avec une importation en vrac unique à mon travail. Sans définir les propriétés ci-dessus, ajouter environ 7500 objets compliqués au contexte prenait plus de 30 minutes.La définition des propriétés ci-dessus (désactivation des vérifications EF et modification du suivi) a réduit l'importation à quelques secondes.

Mais, encore une fois, je ne l'utilise que si vous faites des insertions. Si vous devez mélanger des insertions avec des mises à jour/des suppressions, vous pouvez diviser votre code en deux chemins et désactiver les vérifications EF pour la pièce d'insertion, puis réactiver les vérifications pour le chemin de mise à jour/suppression. J'ai utilisé cette approche avec succès pour contourner le comportement lent DbSet.Add().

+0

C'est certainement incroyable et je pourrais l'essayer et le comparer aux encarts en vrac. Merci pour le partage! Merci aussi de me le rappeler, semble-t-il que j'ai oublié ce commentaire mais j'y reviendrai sûrement demain soir ... –

+0

En essayant, cela semble plus lent que des insertions en vrac donc je ne peux pas utiliser cette approche. En détail je fais 350.000 '.Add()' s (entités sans références à d'autres entités, juste des champs avec des valeurs raisonnables) suivi d'un '.SaveChanges()', les mettant à false avant d'appeler ajoute ou enregistre des changements et retour à la vérité après avoir sauvegardé les changements; prend beaucoup plus de temps que les inserts en vrac, donc je ne prends même pas la peine de le laisser fonctionner. –

+0

Je ne peux pas le croire.Cela a fait ma journée et mon patron sera heureux. Fonctionne comme un charme :) – Alireza

4

Il est extrêmement facile à utiliser et l'extension très rapide ici: https://efbulkinsert.codeplex.com/

Il est appelé « Entity Framework d'insertion en bloc ».

L'extension elle-même est dans l'espace de nom EntityFramework.BulkInsert.Extensions. Donc, pour révéler la méthode d'extension ajouter à l'aide

using EntityFramework.BulkInsert.Extensions; 

Et vous pouvez le faire

context.BulkInsert(entities); 

BTW - Si vous ne souhaitez pas utiliser cette extension pour une raison quelconque, vous pouvez également essayer au lieu de courir db.Articles.Add (article) pour chaque article, pour créer chaque fois une liste de plusieurs articles et ensuite utiliser AddRange (nouveau dans EF version 6, avec RemoveRange) pour les ajouter ensemble au dbcontext.

+0

Que fait-il pour améliorer les performances? – PeterX

+0

Malheureusement, je reçois une erreur «La clé donnée n'était pas présente dans le dictionnaire» et il ne semble pas y avoir une bonne réponse via [ici] (http://stackoverflow.com/a/26427216/845584) – PeterX

+0

Il saute les appels de validation sur chaque ligne, faire une validation à la fin – ScottB