• システム開発に関わる内容をざっくりと書いていく

Entity Framework Core DbUpdateException の切り分け

EF Core で SaveChanges / SaveChangesAsync したときに出る DbUpdateException。内側を見ないと原因が分からないことが多い。

この記事では、よくある中身と切り分け手順をざっくりまとめる。


先に結論

  • DbUpdateException はラッパー。本質は InnerException
  • 多いのは一意制約、FK、必須列、同時実行(concurrency)
  • 例外メッセージだけでなく、どのエンティティ操作かをログに残す

よくある中身

1. 一意制約違反

同じコード・メールを二重登録したときなど。SQL Server なら 2627 / 2601 系。

2. 外部キー制約違反

存在しない親 ID を参照して保存したとき。関連の付け忘れも多い。

3. NOT NULL / 必須プロパティ

必須列に null や空を送っている。クライアント側バリデーション不足でも起きる。

4. 同時実行競合

DbUpdateConcurrencyException(派生)として見えることもある。rowversion 更新競合など。


切り分けコード例

try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
Console.WriteLine(ex.InnerException?.Message);
foreach (var entry in ex.Entries)
{
Console.WriteLine($"{entry.Entity.GetType().Name} : {entry.State}");
}
throw;
}

現場での確認ポイント

  1. InnerException の SQL エラー番号・メッセージを読む
  2. 対象エンティティと State(Added/Modified/Deleted)を確認
  3. 制約(UNIQUE / FK / NOT NULL)のどれかに当たりをつける
  4. 必要なら発行 SQL をログして値を確認する

対策の型

  • 登録前に重複チェック(ただし競合はゼロにできない)
  • FK は存在確認 or ナビゲーションプロパティで正しく紐付け
  • 必須項目は DTO バリデーションで先に落とす
  • 同時更新が想定されるなら concurrency token を設計する

ざっくりまとめ

  • DbUpdateException 単体で悩まない。内側を見る
  • 制約違反と同時実行をまず疑う
  • エンティティ単位のログがあると再発時に強い