web-dev-qa-db-fra.com

Mise à jour / insertion d'entités multiples dans Entity Framework

Juste un aperçu de ce que j'essaie d'accomplir. Nous conservons une copie locale d'une base de données distante (tierce partie) dans notre application. Pour télécharger les informations, nous utilisons une API. Nous téléchargeons actuellement les informations selon un calendrier qui insère ensuite de nouveaux enregistrements dans la base de données locale ou met à jour les enregistrements existants. voici comment cela fonctionne actuellement

public void ProcessApiData(List<Account> apiData)
{
     // get the existing accounts from the local database
     List<Account> existingAccounts = _accountRepository.GetAllList();

     foreach(account in apiData)
     {
         // check if it already exists in the local database
         var existingAccount = existingAccounts.SingleOrDefault(a => a.AccountId == account.AccountId);

         // if its null then its a new record
         if(existingAccount == null)
         {
             _accountRepository.Insert(account);
             continue;
         }

         // else its a new record so it needs updating
         existingAccount.AccountName = account.AccountName;

         // ... continue updating the rest of the properties
     }

     CurrentUnitOfWork.SaveChanges();
}

Cela fonctionne bien, mais il semble que cela pourrait être amélioré.

  1. Il existe une de ces méthodes par entité, et elles font toutes la même chose (simplement en mettant à jour des propriétés différentes) ou en insérant une entité différente. Serait-il possible de rendre cela plus générique?
  2. Il semble juste que beaucoup d'appels de base de données, y aurait-il de toute façon pour "Bulk" faire cela. J'ai jeté un œil à ce package que j'ai vu mentionné dans quelques autres articles https://github.com/loresoft/EntityFramework.Extended Mais il semble se concentrer sur la mise à jour en masse d'une seule propriété avec la même valeur, ou alors je peux dire.

Toutes les suggestions sur la façon dont je peux améliorer cela seraient brillantes. Je suis encore relativement nouveau sur c # donc je cherche toujours la meilleure façon de faire les choses.

J'utilise .net 4.5.2 et Entity Framework 6.1.3 avec MSSQL 2014 comme base de données principale

11
tjackadams
  1. En supposant que les classes dans apiData sont les mêmes que vos entités, vous devriez pouvoir utiliser Attach(newAccount, originalAccount) pour mettre à jour une entité existante.
  2. Pour les insertions groupées, j'utilise AddRange(listOfNewEntitities). Si vous avez beaucoup d'entités à insérer, il est conseillé de les regrouper. Vous pouvez également supprimer et recréer le DbContext sur chaque lot afin qu'il n'utilise pas trop de mémoire.

    var accounts = new List<Account>();
    var context = new YourDbContext();
    context.Configuration.AutoDetectChangesEnabled = false;
    
    foreach (var account in apiData)
    {
        accounts.Add(account);
        if (accounts.Count % 1000 == 0) 
        // Play with this number to see what works best
        {
            context.Set<Account>().AddRange(accounts);
            accounts = new List<Account>();
            context.ChangeTracker.DetectChanges();
            context.SaveChanges();
            context?.Dispose();
            context = new YourDbContext();
        }
    }
    
    context.Set<Account>().AddRange(accounts);
    context.ChangeTracker.DetectChanges();
    context.SaveChanges();
    context?.Dispose();
    

Pour les mises à jour groupées, rien n'est intégré à LINQ to SQL. Il existe cependant des bibliothèques et des solutions pour résoudre ce problème. Voir par exemple Ici pour une solution utilisant des arbres d'expression.

6
Hintham

Pour EFCore, vous pouvez utiliser cette bibliothèque:
https://github.com/borisdj/EFCore.BulkExtensions

Et pour EF 6 celui-ci:
https://github.com/TomaszMierzejowski/EntityFramework.BulkExtensions

Les deux étendent DbContext avec des opérations en bloc et ont le même appel de syntaxe:

context.BulkInsert(entitiesList);
context.BulkUpdate(entitiesList);
context.BulkDelete(entitiesList);

La version EFCore a en plus la méthode BulkInsertOrUpdate.

5
borisdj

Liste vs dictionnaire

Vous vérifiez une liste à chaque fois si l'entité existe qui est mauvaise. Vous devez plutôt créer un dictionnaire pour améliorer les performances.

var existingAccounts = _accountRepository.GetAllList().ToDictionary(x => x.AccountID);

Account existingAccount;

if(existingAccounts.TryGetValue(account.AccountId, out existingAccount))
{
    // ...code....
}

Ajouter vs AddRange

Vous devez être conscient des performances Add vs. AddRange lorsque vous ajoutez plusieurs enregistrements.

  • Ajouter: appeler DetectChanges après l'ajout de chaque enregistrement
  • AddRange: appelez DetectChanges après l'ajout de tous les enregistrements

enter image description here

Ainsi, pour 10 000 entités, la méthode Add a pris 875 fois plus de temps pour ajouter simplement des entités dans le contexte.

Réparer:

  1. Créer une liste
  2. AJOUTER une entité à la liste
  3. UTILISER AddRange avec la liste
  4. Sauvegarder les modifications
  5. Terminé!

Dans votre cas, vous devrez créer une méthode InsertRange dans votre référentiel.

EF étendu

Vous avez raison. Cette bibliothèque met à jour toutes les données avec la même valeur. Ce n'est pas ce que vous recherchez.

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

Cette bibliothèque peut parfaitement convenir à votre entreprise si vous souhaitez améliorer considérablement vos performances.

Vous pouvez facilement effectuer:

  • BulkSaveChanges
  • BulkInsert
  • BulkUpdate
  • BulkDelete
  • BulkMerge

Exemple:

public void ProcessApiData(List<Account> apiData)
{
    // Insert or Update using the primary key (AccountID)
    CurrentUnitOfWork.BulkMerge(apiData);
}
2
Jonathan Magnan