C#エンティティフレームワーク:一括拡張入力メモリの問題

Aug 17 2020

現在、EF拡張機能を使用しています。私が理解していないことの1つは、「パフォーマンスに役立つはずだ」ということです。

ただし、100万を超えるレコードをList変数に配置することは、メモリの問題そのものです。では、すべてをメモリに保持せずに100万件のレコードを更新したい場合、これを効率的に行うにはどうすればよいでしょうか。

を使用しfor loop、バッチで更新する必要がありますか?EFExtensions BulkUpdateには、これをサポートするネイティブ機能がありますか?

例:

var productUpdate = _dbContext.Set<Product>()
    .Where(x => x.ProductType == 'Electronics');  // this creates IQueryable

await productUpdate.ForEachAsync(c => c.ProductBrand = 'ABC Company');

_dbContext.BulkUpdateAsync(productUpdate.ToList());

資源:

https://entityframework-extensions.net/bulk-update

回答

Pac0 Aug 18 2020 at 00:27

クエリのような条件で一括更新を行う「適切な」EF拡張機能の方法を見つけました。

var productUpdate = _dbContext.Set<Product>()
    .Where(x => x.ProductType == 'Electronics')
    .UpdateFromQuery( x => new Product { ProductBrand = "ABC Company" });

これにより、ドキュメントのUPDATE ... SET ... WHEREように、最初にエンティティをロードする必要なしに、適切なSQLが生成されます。

なぜUpdateFromQuery速くよりもSaveChangesBulkSaveChangesBulkUpdate

UpdateFromQueryなどのSQLでステートメントを直接実行しますUPDATE [TableName] SET [SetColumnsAndValues] WHERE [Key]

他の操作では通常、1つまたは複数のデータベースラウンドトリップが必要であり、パフォーマンスが低下します。

の例を応用した、このdotnetフィドルの例で動作する構文を確認できますBulkUpdate

その他の考慮事項

  • 残念ながら、このためのバッチ操作については言及されていません。

  • このような大規模な更新を行う前に、この列にあるインデックスを非アクティブ化し、後で再構築することを検討する価値があるかもしれません。これは、それらが多数ある場合に特に役立ちます。

  • の条件に注意してください。EFWhereによってSQLとして変換できない場合は、クライアント側で実行されます。つまり、「通常の」ひどいラウンドトリップ「ロード-メモリの変更-更新」を意味します。

2 Flater Aug 17 2020 at 23:43

これは実際にはEFが対象としていないものです。EFのデータベースの相互作用は、レコードオブジェクトから始まり、そこから流れます。エンティティが変更追跡されていない(したがってロードされていない)場合、EFは部分的なUPDATEを生成できません(つまり、すべてを上書きしません)。同様に、キーではなく条件に基づいてレコードを削除できません。

次のような条件付き更新/削除ロジックに相当するEFはありません(これらのレコードをすべてロードしない場合)

UPDATE People
SET FirstName = 'Bob'
WHERE FirstName = 'Robert'

または

DELETE FROM People
WHERE FirstName = 'Robert'

EFアプローチを使用してこれを行うには、これらのエンティティをすべてロードして(更新または削除して)データベースに送り返す必要があります。これは、すでにわかっているように、帯域幅とパフォーマンスの無駄です。

ここで私が見つけた最善の解決策は、EFのLINQに適したメソッドをバイパスし、代わりに生のSQLを自分で実行することです。これは、EFコンテキストを使用して実行できます。

using (var ctx = new MyContext())
{
    string updateCommand = "UPDATE People SET FirstName = 'Bob' WHERE FirstName = 'Robert'";
    int noOfRowsUpdated = ctx.Database.ExecuteSqlCommand(updateCommand);

    string deleteCommand = "DELETE FROM People WHERE FirstName = 'Robert'";
    int noOfRowsDeleted = ctx.Database.ExecuteSqlCommand(deleteCommand);
}

詳細はこちら。もちろん、必要に応じてSQLインジェクションから保護することを忘れないでください

raw SQLを実行するための特定の構文は、EF / EF Coreのバージョンごとに異なる場合がありますが、私が知る限り、すべてのバージョンでrawSQLを実行できます。


EF ExtensionsまたはBulkUpdateのパフォーマンスについて具体的にコメントすることはできず、それらから購入するつもりはありません。

彼らのドキュメントに基づくと、条件付きの更新/削除ロジックを可能にする適切な署名を持つメソッドがないようです。

  • BulkUpdate これを最適化できる論理条件(UPDATEコマンドのWHERE)を入力できないようです。
  • BulkDeleteまだBatchSize設定があります。これは、レコードを一度に1つずつ(まあ、バッチごとに)処理しており、条件付きの単一のDELETEクエリを使用していないことを示しています(WHERE句)。

質問で意図したコードに基づくと、EFExtensionsは実際には必要なものを提供していません。EFがエンティティをロードする必要がないため、データベースで生のSQLを実行する方がパフォーマンスが高く安価です。

更新
私は修正されたままであるかもしれません、ここに見られるように、条件付き更新ロジックのいくつかのサポートがあります。ただし、例がまだすべてをメモリにロードしている間、その条件付きWHEREロジックの目的は、すでにすべてをメモリにロードしている場合はどうなるかはわかりません(メモリ内のLINQを使用しないのはなぜですか?)

ただし、これがエンティティをロードせずに機能する場合でも、次のようになります。

  • より制限されています(有効なSQLであるブール条件を許可するSQLと比較して、同等性チェックのみが許可されます)、
  • 比較的複雑です(私はそれらの構文が好きではありません、多分それは主観的です)
  • そしてより高価です(まだ有料の図書館)

独自の生のSQLクエリをローリングするのと比較して。ここで独自の生のSQLをロールすることをお勧めしますが、それは私の意見です。