効率的な更新

バッチ処理

EF Core では、すべての更新を 1 回のラウンドトリップで自動的にバッチ処理することで、ラウンドトリップを最小限に抑えます。 次の点について検討してください。

var blog = context.Blogs.Single(b => b.Url == "http://someblog.microsoft.com");
blog.Url = "http://someotherblog.microsoft.com";
context.Add(new Blog { Url = "http://newblog1.microsoft.com" });
context.Add(new Blog { Url = "http://newblog2.microsoft.com" });
context.SaveChanges();

上の例では、データベースからブログを読み込み、その URL を変更して、2つの新しいブログを追加しています。これを適用するために、2つの SQL INSERT ステートメントと 1 つの UPDATE ステートメントがデータベースに送信されます。 これらを 1 つずつ送信する代わりに、EF Core では Blog インスタンスが追加されるときにこれらの変更を内部的に追跡し、SaveChanges が呼び出さたときに、これらを 1 回のラウンドトリップで実行します。

EF が 1 回のラウンドトリップでバッチ処理するステートメントの数は、使用されているデータベース プロバイダーによって異なります。 たとえば、パフォーマンス分析では、4 つ未満のステートメントが関係している場合、SQL Server では通常バッチ処理は効率が低いことが示されています。 同様に、バッチ処理の利点は SQL Server では約 40 ステートメントあたりで低下し始めるため、EF Core は既定では 1 つのバッチで最大 42 ステートメントのみを実行し、別のラウンドトリップで追加のステートメントを実行します。

ユーザーは、これらのしきい値を調整してパフォーマンスを向上させることができる場合もありますが、これらを変更する場合は慎重にベンチマークを行ってください。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlServer(
        @"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True",
        o => o
            .MinBatchSize(1)
            .MaxBatchSize(100));
}

関連する場合は ExecuteUpdate と ExecuteDelete を使う

すべての従業員の給与を引き上げるとします。 これを EF Core で実装すると、一般的には次のようになります。

foreach (var employee in context.Employees)
{
    employee.Salary += 1000;
}
context.SaveChanges();

これは完全に有効なコードですが、パフォーマンスの観点からその内容を分析してみましょう。

  • すべての関連する従業員を読み込むために、データベースのラウンドトリップが実行されます。これにより、給与のみが必要な場合でも、従業員のすべての行データがクライアントに取り込まれることに注意してください。
  • EF Core の変更追跡では、エンティティの読み込み時にスナップショットを作成し、それらのスナップショットをインスタンスと比較して、どのプロパティが変更されたかを確認します。
  • 通常、すべての変更を保存するために 2 回目のデータベース ラウンドトリップが実行されます (データベース プロバイダーによっては、変更が複数のラウンドトリップに分割される場合があることに注意してください)。 このバッチ動作は、更新ごとにラウンドトリップを行うよりもはるかに優れていますが、EF Core からは依然として従業員ごとに UPDATE ステートメントが送信されるので、データベースでは各ステートメントを個別に実行する必要があります。

EF Core 7.0 以降では、ExecuteUpdateExecuteDelete の各メソッドを使って、同じことをはるかに効率的に実行できるようになりました。

context.Employees.ExecuteUpdate(s => s.SetProperty(e => e.Salary, e => e.Salary + 1000));

これにより、次の SQL ステートメントがデータベースに送信されます。

UPDATE [Employees] SET [Salary] = [Salary] + 1000;

この UPDATE により、すべての操作が 1 回のラウンドトリップで実行されます。データベースに実際のデータが読み込まれたり送信されたりすることはなく、追加のオーバーヘッドが発生する EF の変更追跡メカニズムを使う必要もありません。 詳細については、次のトピックを参照してください。 ExecuteUpdate および ExecuteDelete

ExecuteUpdateExecuteDelete をまだサポートしていない旧バージョンの EF Core を使っている場合や、これらのメソッドではサポートされていない複雑な SQL ステートメントを実行する場合は、SQL クエリを使って次の操作を実行できます。

context.Database.ExecuteSql($"UPDATE [Employees] SET [Salary] = [Salary] + 1000");

SaveChangesExecuteUpdate/ExecuteDelete の違いについては、データの保存に関する 概要のページを参照してください。