クエリを実行するとデータベースからデータを読み取ることができますが、データを保存すると、新しいエンティティをデータベースに追加したり、エンティティを削除したり、既存のエンティティのプロパティを何らかの方法で変更したりできます。 Entity Framework Core (EF Core) では、データベースにデータを保存するための 2 つの基本的な方法がサポートされています。
方法 1: Change Tracking と SaveChanges
多くのシナリオでは、プログラムでデータベースからのデータのクエリを実行し、それに対していくつかの変更を実行し、それらの変更を保存する必要があります。これは"作業単位" と呼ばれることもあります。 たとえば、一連のブログがあり、そのうちの 1 つの Url プロパティを変更するとします。 EF では、これは通常、次のように行われます。
using (var context = new BloggingContext())
{
var blog = await context.Blogs.SingleAsync(b => b.Url == "http://example.com");
blog.Url = "http://example.com/blog";
await context.SaveChangesAsync();
}
上記のコードでは、次の手順を実行します。
- 通常の LINQ クエリを使用して、データベースからエンティティを読み込みます ( クエリ データを参照)。 EF のクエリは既定で追跡されます。つまり、EF は内部 変更トラッカーで読み込まれたエンティティを追跡します。
- 読み込まれたエンティティ インスタンスは、.NET プロパティを割り当てることによって、通常どおりに操作されます。 EF はこの手順に関与しません。
- 最後に、 DbContext.SaveChanges() が呼び出されます。 この時点で、EF は、エンティティを読み込まれた時点からのスナップショットと比較することで、変更を自動的に検出します。 検出された変更はデータベースに保存されます。リレーショナル データベースを使用する場合、これは通常、関連する行を更新する SQL
UPDATEなどを送信する必要があります。
上記で説明した既存のデータの一般的な更新操作ですが、エンティティの 追加 と 削除 については同様の原則が保持されることに注意してください。 EF の変更トラッカーを操作するには、 DbSet<TEntity>.Add と Removeを呼び出して変更を追跡します。 その後、EF は、 SaveChanges() が呼び出されたときに、追跡されたすべての変更をデータベースに適用します (たとえば、SQL INSERT 経由で、リレーショナル データベースを使用する場合は DELETE )。
SaveChanges() には、次の利点があります。
- 変更されたエンティティとプロパティを追跡するコードを記述する必要はありません。EF はこれを自動的に実行し、データベース内のこれらのプロパティのみを更新してパフォーマンスを向上させます。 読み込まれたエンティティが UI コンポーネントにバインドされ、ユーザーが必要なプロパティを変更できる場合を想像してください。EF は、実際に変更されたエンティティとプロパティを把握する負担を取り除きます。
- データベースへの変更の保存が複雑になる場合があります。 たとえば、ブログを追加し、そのブログに関連するいくつかの投稿を追加したい場合、投稿を挿入する前に、挿入されたブログのデータベースで生成されたキーを取得する必要があります(投稿がブログを参照する必要があるため)。 EF は、複雑さを取り除いて、これをすべて行います。
- EF は、クエリと SaveChanges()の間で他のユーザーによってデータベース行が変更された場合など、コンカレンシーの問題を検出できます。 詳細については、 コンカレンシーの競合に関するページを参照してください。
- これをサポートするデータベースでは、 SaveChanges() はトランザクションで複数の変更を自動的にラップし、障害が発生した場合でもデータの一貫性を維持します。 詳細については、「 トランザクション」を参照してください。
- SaveChanges() また、多くの場合、複数の変更をバッチ処理して、データベースラウンドトリップの数を大幅に減らし、パフォーマンスを大幅に向上させます。 詳細については、「 効率的な更新」を参照してください。
基本的な SaveChanges() の使用方法の詳細とコード サンプルについては、「 基本的な SaveChanges」を参照してください。 EF の変更の追跡の詳細については、 変更の追跡の概要を参照してください。
方法 2: ExecuteUpdate と ExecuteDelete ("一括更新")
変更の追跡と 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()の呼び出しが必要になる、複雑な場合があります。 このようなシナリオでは、 ExecuteUpdate と ExecuteDelete は、同じ操作を表すかなり簡単な方法です。
最後に、変更の追跡と SaveChanges() 自体の両方で、特定のランタイム オーバーヘッドが発生します。 高パフォーマンスのアプリケーションを作成する場合は、 ExecuteUpdate と ExecuteDelete これらのコンポーネントの両方を回避し、必要なステートメントを効率的に生成できます。
ただし、 ExecuteUpdate と ExecuteDelete には、次のような制限もあります。
- これらのメソッドはすぐに実行され、現在、他の操作と一緒にバッチ処理することはできません。 一方、 SaveChanges()、複数の操作をまとめてバッチ処理できます。
- 変更の追跡は関係しないため、変更する必要があるエンティティとプロパティを正確に把握する必要があります。 これは、何を変更する必要があり、何を変更しないかを追跡する、より手動の低レベルのコード追跡を意味する可能性があります。
- さらに、変更の追跡は関係しないため、これらのメソッドは変更を保持するときにコン カレンシー制御 を自動的に適用しません。 ただし、
Where句を明示的に追加して、コンカレンシー制御を自分で実装することはできます。 - 現在、更新と削除のみがサポートされています。挿入は DbSet<TEntity>.Add と SaveChanges() を使用して行う必要があります。
詳細とコード サンプルについては、「 ExecuteUpdate と ExecuteDelete」を参照してください。
概要
次に、どの方法を使用するかに関するいくつかのガイドラインを示します。 これらは絶対的なルールではありませんが、便利な経験則を提供します。
- どの変更が行われるかわからない場合は、
SaveChangesを使用します。適用する必要がある変更が自動的に検出されます。 サンプル シナリオ:- "データベースからブログを読み込み、ユーザーが変更できるフォームを表示したい"
- オブジェクトのグラフ (つまり、複数の相互接続されたオブジェクト) を操作する必要がある場合は、
SaveChangesを使用します。変更の適切な順序と、すべてをリンクする方法が示されます。- "ブログを更新し、その投稿の一部を変更し、他の投稿を削除したい"
- 何らかの条件に基づいて多数のエンティティを変更する場合は、
ExecuteUpdateとExecuteDeleteを使用します。 サンプル シナリオ:- 「全社員に昇給したい」
- "名前が X で始まるすべてのブログを削除したい"
- 変更するエンティティとその変更方法が既にわかっている場合は、
ExecuteUpdateとExecuteDeleteを使用します。 サンプル シナリオ:- "名前が 'Foo' のブログを削除したい"
- "Id 5 のブログの名前を 'Bar' に変更したい"
.NET