目次
概要
Entity Framework Core (EF Core) を使用して、データベース内の既存レコードを変更し、確定(コミット)する基本的な実装パターンです。 EF Core の「変更追跡(Change Tracking)」機能を利用し、オブジェクトのプロパティを書き換えて SaveChangesAsync を呼ぶだけで、自動的に適切な UPDATE SQLが発行されます。
仕様(入出力)
- 入力: 更新対象の特定条件(例: 商品名)、変更後の新しい値。
- 出力: 更新処理の結果(更新前後の値をコンソール表示)。
- 前提: .NET 6.0以上、Microsoft.EntityFrameworkCore.InMemory(動作確認用)。
基本の使い方
- データを取得する(追跡状態にする)。
- プロパティを変更する。
SaveChangesAsyncで変更をデータベースに反映する。
// 1. 取得(Firstなどを使う)
var product = await context.Products
.FirstAsync(p => p.Name == "Old Product");
// 2. 変更(値を変えるだけ)
product.Price = 1200;
product.LastUpdated = DateTime.Now;
// 3. 保存(変更があった箇所のみUPDATE文が発行される)
await context.SaveChangesAsync();
コード全文
ここでは「商品データの価格改定」を行うシナリオです。 動作確認のためインメモリデータベースを使用しています。
外部ライブラリとして Microsoft.EntityFrameworkCore.InMemory が必要です。
dotnet add package Microsoft.EntityFrameworkCore.InMemory
using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
public class Program
{
public static async Task Main()
{
var options = new DbContextOptionsBuilder<ShopDbContext>()
.UseInMemoryDatabase("ShopDb")
.Options;
// --- 1. 初期データの準備 ---
using (var context = new ShopDbContext(options))
{
if (!await context.Products.AnyAsync())
{
context.Products.Add(new Product
{
Name = "Standard Plan",
Price = 1000,
LastUpdated = DateTime.Now.AddDays(-10)
});
await context.SaveChangesAsync();
}
}
// --- 2. データの更新処理 ---
using (var context = new ShopDbContext(options))
{
var worker = new ProductManager(context);
// "Standard Plan" の価格を 1500 に変更
await worker.UpdateProductPriceAsync("Standard Plan", 1500);
}
// --- 3. 結果の確認 ---
using (var context = new ShopDbContext(options))
{
var product = await context.Products.FirstAsync(p => p.Name == "Standard Plan");
Console.WriteLine($"[確認] 商品名: {product.Name}");
Console.WriteLine($" 価格: {product.Price}");
Console.WriteLine($" 更新日時: {product.LastUpdated}");
}
}
}
// 業務ロジッククラス
public class ProductManager
{
private readonly ShopDbContext _context;
public ProductManager(ShopDbContext context)
{
_context = context;
}
public async Task UpdateProductPriceAsync(string productName, decimal newPrice)
{
// データを取得(デフォルトで変更追跡が有効)
var product = await _context.Products
.FirstOrDefaultAsync(p => p.Name == productName);
if (product == null)
{
Console.WriteLine($"エラー: 商品 '{productName}' は見つかりませんでした。");
return;
}
Console.WriteLine($"[更新前] {product.Name}: {product.Price}円 ({product.LastUpdated})");
// プロパティを変更
product.Price = newPrice;
product.LastUpdated = DateTime.Now;
// データベースへ反映
// 変更されたプロパティのみがUPDATE文に含まれます
int affectedRows = await _context.SaveChangesAsync();
Console.WriteLine($"[更新完了] {affectedRows}件のデータを保存しました。");
}
}
// エンティティ定義
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public DateTime LastUpdated { get; set; }
}
// DbContext定義
public class ShopDbContext : DbContext
{
public ShopDbContext(DbContextOptions<ShopDbContext> options)
: base(options) { }
public DbSet<Product> Products => Set<Product>();
}
実行結果例
[更新前] Standard Plan: 1000円 (2025/01/01 10:00:00)
[更新完了] 1件のデータを保存しました。
[確認] 商品名: Standard Plan
価格: 1500
更新日時: 2025/01/11 10:00:00
カスタムポイント
- 更新日時の自動化: エンティティのプロパティ変更で手動セットする代わりに、
DbContextのSaveChangesをオーバーライドして、変更されたエンティティのLastUpdatedを一律で現在時刻にする手法も一般的です。 - 部分更新: EF Core は変更された列だけを
UPDATE文に含めます。全ての列を更新したいわけではない場合も、このコードのままで最適化されています。 - Attachの使用: Web APIなどで、画面から受け取ったオブジェクト(DBから読み込んでいない状態)を更新したい場合は、
context.Products.Attach(product)を使い、EntityState.Modifiedを設定するパターンを使用します。
注意点
- 取得漏れ:
FirstOrDefaultAsyncは見つからない場合にnullを返します。そのままプロパティにアクセスするとNullReferenceExceptionになるため、必ずnullチェックを行ってください。 - SaveChangesAsyncの呼び忘れ: プロパティを変えただけではデータベースには反映されません。必ず
SaveChangesAsyncを呼び出してください。 - 同時実行制御(Concurrency): 取得してから保存するまでの間に、別のユーザーが同じデータを書き換えている可能性があります。厳密な制御が必要な場合は、
[Timestamp]属性(行バージョン)を使用し、DbUpdateConcurrencyExceptionをハンドリングする必要があります。
応用
EF Core 7.0以降では、データをメモリに読み込まずにデータベース上で直接一括更新する ExecuteUpdateAsync が使用可能です。大量データの更新で非常に高速です。
// 取得処理(SELECT)を行わず、条件に一致する全行を直接UPDATEする
await context.Products
.Where(p => p.Price < 1000)
.ExecuteUpdateAsync(s => s
.SetProperty(p => p.Price, p => p.Price * 1.1m) // 10%値上げ
.SetProperty(p => p.LastUpdated, DateTime.Now));
まとめ
パフォーマンスを最優先する一括更新では、EF Core 7以降の ExecuteUpdateAsync の利用を検討してください。
通常の実装では「取得」→「書き換え」→「SaveChangesAsync」の3ステップが基本です。
EF Coreの変更追跡機能により、差分のみが効率的にSQL化されます。
