2016-03-27 4 views
3

J'utilise EF 6. J'essaie d'insérer environ 200.000 entités tout en sauvegardant les modifications de base de données après chaque 100 entités.Entity Framework bulk insert unreal lent

Le problème est qu'il a fallu 11 heures pour sauver 50.000 entités, et il est toujours en retard. Je cours ceci avec WebJobs, et le travail est édité sur la même webapp d'azure que le site Web principal. Est-ce le problème à cause de cela et WebJob n'a pas assez de ressources, ou d'économiser après 100 entités, ou l'approche?

Méthode

public void SaveLeadsForBuyer(ISenderModel model) 
{ 
    var rowCounter = 0; 

    foreach (var deliveryRecord in model.Customers.Select(customerModel => new DeliveryRecord() 
    { 
     BuyerId = model.Buyer.Id, 
     AspNetUserId = customerModel.Id, 
     DeliveryType = model.Buyer.DeliveryType, 
     CreatedOn = DateTime.UtcNow 
    })) 
    { 
     ++rowCounter; 

     _unit.Repository<DeliveryRecord>().Insert(deliveryRecord); 

     _unit.SaveChangesPartially(rowCounter, 100); 
    } 

    _unit.SaveChanges(); 
} 

Aide

public static class UnitOfWorkHelper 
{ 
    /// <summary> 
    /// Helper method triggers SaveChanges() after amount of rows provided through "amount" parameter in method 
    /// </summary> 
    /// <param name="unit">UnitOfWork object</param> 
    /// <param name="count">Current amount of rows</param> 
    /// <param name="saveCount">Amount when to save changes to database</param> 
    public static void SaveChangesPartially(this IUnitOfWorkAsync unit, int count, int saveCount) 
    { 
     if (count % saveCount == 0) 
     { 
      unit.SaveChanges(); 
     } 
    } 
} 
+0

EF est très mauvais pour l'insertion en masse, 'INSERT SELECT' fonctionnera 1000000 fois plus vite que EF. –

+2

Ce n'est pas ** un "encart groupé" que vous faites - vous devez regarder des composants comme ['EntityFramework.BulkInsert'] (https://efbulkinsert.codeplex.com/) ou autres (* * recherche ** sur votre moteur de recherche préféré pour "Entity Framework bulk insert" - vous en trouverez plusieurs, choisissez celui que vous préférez) –

Répondre

5

lent parce que Entity Framework effectue une base de données pour chaque voyage aller-retour des documents. Ainsi, si vous sauvegardez 200 000 entités, 200 000 aller-retour de base de données sera effectué, ce qui est loin d'être optimal pour sauver plusieurs entités.

Pour ce genre de scénario, vous devez vous mettre en œuvre ou d'utiliser une bibliothèque de soutien BulkInsert (qui exécutent normalement une SqlBulkCopy sous le capot)

Il y a 3 bibliothèque principale (2 FREE, 1 PRO) qui permettent en vrac insérez

// Example from Entity Framework Extensions Library 
using (var ctx = new EntitiesContext()) 
{ 
    ctx.BulkInsert(list); 
} 

Vous pouvez lire l'article ci-dessous pour comprendre PROS & CONS pour chaque bibliothèque: Entity Framework - Bulk Insert Library Reviews & Comparisons

Entity Framework Extensions est la bibliothèque qui offre de loin t Il est le plus flexible (Bulk Insert, Update, Delete, Merge et BulkSaveChanges et supporte tout) mais est une version PRO. Si vous cherchez une version gratuite, je recommande d'utiliser EntityFramework.BulkInsert mais ce n'est plus supporté et ne supporte pas toutes les associations et les héritages.

Responsabilité: Je suis le propriétaire du projet Entity Framework Extensions

EDIT: réponse Commentaire question

Je sauve chaque 100 enregistrements, et non chaque enregistrement

Peu importe si vous ajoutez une entité ou 100 entités à votre contexte d'unité, Entity Framework les enregistre un par un (un seul dans déclaration de sert pour chaque enregistrement). Utilisez simplement SQL Profiler avec une base de données SQL Server et vous verrez ce que je veux dire.

EDIT: réponse Commentaire question

grand jonathan. est-il un moyen de mettre en œuvre cela avec ef6 générique uow?

La réponse dépend de la bibliothèque que vous choisissez d'utiliser.

Si vous utilisez ma bibliothèque, vous pouvez créer la méthode BulkSaveChanges ou changer dans votre UnitOfWork tous « (_context.SaveChanges) » par « _context.BulkSaveChanges() »

public void SaveLeadsForBuyer(ISenderModel model) 
{ 
    // ... code ... 
    // _unit.SaveChanges(); 
    _unit.BulkSaveChanges(); 
} 

Si vous voulez la meilleure performance et mettre en œuvre d'insertion en bloc de ma bibliothèque ou une bibliothèque gratuite, je serais probablement ajouter une méthode ou une méthode d'extension (si vous ne pouvez pas changer la classe de référentiel) nommé BulkInsert

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class 
{ 
    // ... code ... 

    public virtual void BulkInsert(List<TEntity> list) 
    { 
     _context.BulkInsert(list); 
    } 
} 

Gardez à l'esprit BulkInsert insérer directement des entités sans avoir à appelez "SaveChanges", il n'utilise pas le contexte/change tracker pour obtenir les performances optimales.

+0

Je sauvegarde 100 enregistrements, pas chaque enregistrement. –

+0

Peu importe. Vous utilisez un outil inadapté au travail. EF n'est pas conçu pour les transferts groupés. Comme dit Jonathan - cela dit, SqlBulkCopy est écrit par quelqu'un qui devrait être renvoyé pour cela (comportement de verrouillage extrêmement mauvais) ... J'ai écrit ma propre classe en une demi-journée qui crée une table temporaire, sql bulk copies dedans, puis utilise une commande sql pour copier les données dans la table finale. Évite la logique de verrouillage ridicule dans SqlBulkCopy (qui essaie d'obtenir un verrou exclusif pendant 30 secondes, dans une boucle sans attendre, donc s'il y a une activité sur la table, elle ne l'obtient jamais). – TomTom

+0

grand jonathan. est-il possible de l'implémenter avec ef6 générique uow? –