web-dev-qa-db-ja.com

Entity Frameworkの複数のエンティティの更新/挿入

私が達成しようとしていることのほんの少しの概要。アプリケーション内にリモートデータベース(サードパーティ)のローカルコピーを保持します。情報をダウンロードするには、APIを使用します。現在、スケジュールに基づいて情報をダウンロードし、ローカルデータベースに新しいレコードを挿入するか、既存のレコードを更新します。現在の仕組みは次のとおりです

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();
}

これは正常に機能しますが、改善できるように感じます。

  1. エンティティごとにこれらのメソッドの1つがあり、それらはすべて同じこと(異なるプロパティを更新するだけ)を実行するか、異なるエンティティを挿入します。とにかくこれをより一般的にすることはありますか?
  2. 多くのデータベース呼び出しのように思えますが、とにかく「一括」する必要があります。私は他のいくつかの投稿で言及されているこのパッケージを見てきました https://github.com/loresoft/EntityFramework.Extended しかし、単一のプロパティの一括更新に焦点を当てているようです同じ値で、またはそう言うことができます。

私がこれをどのように改善できるかについての提案は素晴らしいでしょう。私はまだc#にかなり慣れていないので、物事を行うための最良の方法を探しています。

バックエンドデータベースとしてMSSQL 2014で.net 4.5.2とEntity Framework 6.1.3を使用しています

11
tjackadams
  1. ApiDataのクラスがエンティティと同じであると仮定すると、Attach(newAccount, originalAccount)を使用して既存のエンティティを更新できるはずです。
  2. 一括挿入には、AddRange(listOfNewEntitities)を使用します。挿入するエンティティが多数ある場合は、それらをバッチ処理することをお勧めします。また、各バッチでDbContextを破棄して再作成して、メモリを使いすぎないようにすることもできます。

    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();
    

一括更新の場合、LINQ to SQLには何も組み込まれていません。ただし、これに対処するライブラリとソリューションがあります。例参照 ここ 式ツリーを使用したソリューションの場合。

6
Hintham

EFCoreの場合、このライブラリを使用できます。
https://github.com/borisdj/EFCore.BulkExtensions

EF 6の場合、これは次のとおりです。
https://github.com/TomaszMierzejowski/EntityFramework.BulkExtensions

両方ともバルク操作でDbContextを拡張しており、同じ構文呼び出しを持っています:

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

EFCoreバージョンにはBulkInsertOrUpdateメソッドが追加されています。

5
borisdj

リストと辞書

悪いエンティティが存在する場合、毎回リストをチェックインします。代わりに、パフォーマンスを向上させるために辞書を作成する必要があります。

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

Account existingAccount;

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

Add vs. AddRange

複数のレコードを追加するときは、AddとAddRangeのパフォーマンスに注意する必要があります。

  • 追加:すべてのレコードが追加された後にDetectChangesを呼び出します
  • AddRange:すべてのレコードが追加された後にDetectChangesを呼び出します

enter image description here

したがって、エンティティが10,000個の場合、Addメソッドは、コンテキストにエンティティを単純に追加するのに875倍の時間がかかりました。

修正するには:

  1. リストを作成する
  2. エンティティをリストに追加
  3. リストでAddRangeを使用する
  4. 変更内容を保存
  5. できた!

あなたの場合、InsertRangeメソッドをリポジトリに作成する必要があります。

EF拡張

あなたが正しいです。このライブラリは、すべてのデータを同じ値で更新します。それはあなたが探しているものではありません。

免責事項:私はプロジェクトの所有者です Entity Framework Extensions

パフォーマンスを劇的に改善したい場合、このライブラリは企業に完全に適合します。

以下を簡単に実行できます。

  • BulkSaveChanges
  • BulkInsert
  • BulkUpdate
  • BulkDelete
  • BulkMerge

例:

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