データの保存

クエリの実行ではデータベースからデータを読み取ることができる一方で、データの保存はデータベースへの新しいエンティティの追加、エンティティの削除、既存エンティティのプロパティの何らかの方法での変更を意味します。 Entity Framework Core (EF Core) では、データベースにデータを保存するための 2 つの基本的な方法がサポートされています。

方法 1: 変更の追跡と SaveChanges

多くのシナリオでは、プログラムでデータベースの一部のデータに対してクエリを実行し、何らかの変更を行い、それらの変更を保存し直す必要があります。これは、"作業単位" と呼ばれることもあります。 たとえば、ブログのセットがあり、そのうちの 1 つの Url プロパティを変更するとします。 通常、EF ではこれは次のように行われます。

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Single(b => b.Url == "http://example.com");
    blog.Url = "http://example.com/blog";
    context.SaveChanges();
}

上のコードは、次の手順を実行します。

  1. 通常の LINQ クエリを使用して、データベースからエンティティを読み込みます (「データのクエリ」を参照)。 EF のクエリは既定で追跡します。つまり、EF はその内部 "変更トラッカー" で読み込まれたエンティティを追跡します。
  2. 読み込まれたエンティティ インスタンスは、.NET プロパティを割り当てることによって通常どおりに操作されます。 EF はこの手順には関与しません。
  3. 最後に、DbContext.SaveChanges() が呼び出されます。 この時点で、EF は、エンティティをエンティティが読み込まれた時点のスナップショットと比較することで、変更を自動的に検出します。 検出された変更はすべてデータベースに保持されます。通常、リレーショナル データベースを使用した場合、関連する行を更新するための SQL UPDATE などの送信も含まれます。

上記では、既存のデータに対する一般的な更新操作を説明しましたが、エンティティの "追加" と "削除" にも同様の原則が適用されることに注意してください。 EF の変更トラッカーを操作するには DbSet<TEntity>.AddRemove を呼び出します。これにより、変更が追跡されるようになります。 その後、EF は、SaveChanges() が呼び出されるときにすべての追跡された変更をデータベースに適用します (リレーショナル データベースを使った場合の SQL INSERT および DELETE の使用など)。

SaveChanges() には次のような利点があります。

  • 変更されたエンティティとプロパティを追跡するコードを記述する必要はありません。EF がこれを自動的に実行し、データベース内のこれらのプロパティのみを更新してパフォーマンスを向上させます。 読み込まれたエンティティが UI コンポーネントにバインドされ、ユーザーが必要なプロパティを変更できることを想像してみてください。EF は、実際にどのエンティティとプロパティが変更されたかを把握する負担を取り除きます。
  • データベースに対する変更の保存が複雑になる場合があります。 たとえば、ブログとそのブログのいくつかの投稿を追加したい場合は、挿入されたブログのデータベース生成キーをフェッチしてから、投稿を挿入する必要があります (ブログを参照する必要があるため)。 EF はこれをすべて自動で行うため、複雑さがなくなります。
  • EF では、クエリと SaveChanges() の間で他のユーザーによってデータベース行が変更された場合など、コンカレンシーの問題を検出できます。 詳細については、「コンカレンシーの競合」を参照してください。
  • それがサポートされているデータベースでは、SaveChanges() によりトランザクションでの複数の変更が自動的にラップされるため、障害が発生した場合でもデータの一貫性が保たれます。 詳細については、「トランザクション」を参照してください。
  • また、SaveChanges() は多くの場合、複数の変更をバッチ処理するため、データベース ラウンドトリップの数が大幅に減り、パフォーマンスが大幅に向上します。 詳細については、「効率的な更新」を参照してください。

基本的な SaveChanges() の使用方法の詳細とコード サンプルについては、「基本的な SaveChanges」を参照してください。 EF の変更追跡の詳細については、「変更の追跡の概要」を参照してください。

方法 2: ExecuteUpdate と ExecuteDelete ("一括更新")

Note

この機能は EF Core 7.0 で導入されました。

変更の追跡と SaveChanges() は変更を保存するための強力な手段ですが、欠点もあります。

まず、SaveChanges() では、変更または削除するすべてのエンティティに対してクエリを実行して追跡する必要があります。 たとえば、評価が特定のしきい値を下回るすべてのブログを削除する必要がある場合は、おそらく膨大な数の行にクエリを実行し、具体化、追跡し、SaveChanges() によりそれぞれの行すべてに対して DELETE ステートメントを生成する必要があります。 リレーショナル データベースでは、1 つの DELETE コマンドを送信して WHERE 句を使用して削除する行を指定するというはるかに効率的な代替手段が提供されますが、SaveChanges() モデルではそれを生成するための効率的な手段は用意されていません。

この "一括更新" シナリオに対応するには、次のように ExecuteDelete を使用できます。

context.Blogs.Where(b => b.Rating < 3).ExecuteDelete();

これにより、通常の LINQ クエリと同様に、通常の LINQ 演算子を使用して SQL DELETE ステートメントを表現できるため、データベースに対して次の SQL が実行されます。

DELETE FROM [b]
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

これにより、データベースでの実行が非常に効率的になり、データベースからデータを読み込んだり、EF の変更トラッカーを使用したりする必要がなくなります。 同様に、ExecuteUpdate では SQL UPDATE ステートメントを表すことができます。

エンティティを一括で変更しない場合でも、変更するエンティティのプロパティを正確に把握できます。 変更追跡 API を使用した変更の実行は、過度に複雑になる可能性があり、エンティティ インスタンスを作成したり、Attach を使用してそれを追跡したり、変更を行ったり、最後に SaveChanges() を呼び出したりする必要があります。 このようなシナリオでは、ExecuteUpdateExecuteDelete は、同じ操作を表現するかなりシンプルな方法になり得ます。

最後に、変更の追跡と SaveChanges() それ自体の両方で、特定のランタイム オーバーヘッドが発生します。 ハイ パフォーマンス アプリケーションを作成している場合、ExecuteUpdateExecuteDelete を使用すると、これらのコンポーネントの両方を回避し、目的のステートメントを効率的に生成できるようになります。

ただし、ExecuteUpdateExecuteDelete には特定の制限もあることに注意してください。

  • これらのメソッドは直ちに実行され、現在は他の操作と一緒にバッチ処理することはできません。 一方、SaveChanges() では複数の操作を一括処理できます。
  • 変更の追跡は関係しないので、変更する必要があるエンティティとプロパティを正確に把握するのはユーザーの責任です。 これは、何を変更する必要があり、何を変更する必要がないかを追跡する、より手動で低レベルのコードを意味する可能性があります。
  • さらに、変更の追跡は関係しないため、これらのメソッドは変更を保持するときに自動的にコンカレンシー制御を適用しません。 ただし、Where 句を明示的に追加して、コンカレンシー制御を自分で実装することはできます。
  • 現在、更新と削除のみがサポートされています。挿入は DbSet<TEntity>.AddSaveChanges() を使用して行う必要があります。

詳細およびコード サンプルについては、「ExecuteUpdateExecuteDelete」を参照してください。

まとめ

次に、どの方法をいつ使用するかを示すガイドラインをいくつか示します。 これらは鉄則ではありませんが、経験則として役立つことに注意してください。

  • どの変更が行われるかわからない場合は、SaveChanges を使用します。適用する必要がある変更が自動的に検出されます。 シナリオ例:
    • 「データベースからブログを読み込み、ユーザーがそれを変更するために使用できるフォームを表示したい」
  • オブジェクト (複数の相互接続されたオブジェクト) のグラフを操作する必要がある場合は、SaveChanges を使用します。変更の適切な順序と、すべてをリンクさせる方法が見つかります。
    • 「その投稿の一部を変更し、他の投稿を削除して、ブログを更新したい」
  • 巨大化する可能性を秘めている多数のエンティティを何らかの条件に基づいて変更する場合は、ExecuteUpdateExecuteDelete を使用します。 シナリオ例:
    • 「すべての従業員の給与を引き上げたい」
    • 「名前が X で始まるすべてのブログを削除したい」
  • 変更するエンティティとそれらを変更する方法が既にわかっている場合は、ExecuteUpdateExecuteDelete を使用します。 シナリオ例:
    • 「"Foo" とい名前のブログを削除したい」
    • 「Id 5 のブログの名前を "Bar" に変更したい」