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

EF Core N+1 の見つけ方と対策

一覧画面が遅い、ログに同じような照会が何百回も出る——典型が N+1。親を1回取ったあと、関連を行ごとに追加で取りに行くパターンだ。EF Core では Include 忘れや遅延ロード、ループ内の別クエリが原因になりやすい。

先に結論

  • まず発行SQL(またはコマンドログ)で「同じ表への連続読み」を確認する
  • 対策の第一候補は Include / ThenInclude、または投影(必要な列だけ DTO)
  • ループ内で FirstAsync や別 DbSet を叩いていないかを疑う
  • 遅延ロードを有効にしているなら、本番前にオフ+明示ロードへ寄せるのが無難

見つけ方

  • Microsoft.EntityFrameworkCore.Database.Command を Information にしてコマンド数を見る
  • 1リクエストで「親1 + 子N」の形になっていないか数える
  • デバッガや MiniProfiler でリクエスト単位のクエリ一覧を見る
  • 画面上は速いがバッチ処理だけ遅い、も同じ症状になりやすい

よくある原因

  • コレクションを foreach しながらナビゲーションプロパティに触る
  • Include なしで関連を参照(遅延ロード or 暗黙の追加照会)
  • サービス層で「ID のリストを回して1件ずつ取得」
  • Include の付けすぎで巨大結合になり、別問題に化けることもある

関連: EF Core DbUpdateException の切り分け

対策の選び方

  • Include: 関連をまとめて取る。深さに注意
  • 投影: 画面に必要な列だけ Select して DTO 化(過剰 Include を避けやすい)
  • 一括取得: ID 群を Contains でまとめて取り、メモリで紐付け
  • 分割クエリ: 巨大結合が重いときは AsSplitQuery も検討

ざっくりまとめ

  • 症状は「コマンド数が行数に比例」
  • ログで見つけ、Include/投影/一括取得で潰す
  • Include 過多も別の遅さの原因になるので、必要最小限にする

関連記事