🔥

Entity Frameworkが勝手に重くなる理由と対策

に公開

Entity Framework(EF Core)は、.NET開発者にとって非常に強力なORMですが、何も考えずに使うと「勝手に重くなる」ことがあります。

本記事では、EF Coreを使っているうちに気づかぬうちに発生しがちなパフォーマンス劣化の原因と、その具体的な対策を紹介します。


✅ 原因1:不要な .Include() の多用

問題点

  • 必要以上にリレーション先を読み込むと、SQLが巨大化し、JOIN地獄に。
  • パフォーマンスに大きな影響を与える。

対策

  • 必要なときだけ Include を使う。
  • Projection(.Select(...))で必要なフィールドだけ取り出す。
// Bad
var orders = dbContext.Orders.Include(o => o.Customer).ToList();

// Good
var orders = dbContext.Orders
    .Select(o => new {
        o.Id,
        o.OrderDate,
        CustomerName = o.Customer.Name
    }).ToList();

✅ 原因2: .ToList() の早すぎる呼び出し

問題点

  • .ToList() によってクエリが即実行される。
  • フィルターやページングの前に呼ぶと、全件メモリに読み込まれる。

対策

  • フィルターや Skip / Take など、必要な処理をクエリ上で完結させてから .ToList()
// Bad
var allUsers = dbContext.Users.ToList();
var activeUsers = allUsers.Where(u => u.IsActive);

// Good
var activeUsers = dbContext.Users.Where(u => u.IsActive).ToList();

✅ 原因3:トラッキングの無駄

問題点

  • 読み取り専用のクエリでも、デフォルトでトラッキング(変更監視)される。
  • 大量データを扱うとメモリ圧迫。

対策

  • .AsNoTracking() を使ってトラッキングを無効化。
// Good for read-only
var users = dbContext.Users.AsNoTracking().ToList();

✅ 原因4:ナビゲーションプロパティの遅延読み込み(Lazy Loading)

問題点

  • foreach やプロパティ参照時に裏でSQLが何度も発行される「N+1問題」。

対策

  • 明示的に Include するか、Select で取得する。
  • LazyLoadingは無効化を検討(UseLazyLoadingProxies を使わない)。

✅ 原因5:DbContextの使いまわしすぎ

問題点

  • 長時間生きるDbContextはメモリやパフォーマンス上の問題を引き起こす。

対策

  • スコープを短く、DIの AddScoped を基本とする。

✅ 原因6:IQueryableの使い方が危険

問題点

  • IQueryable を返すRepositoryなどは、外側で複雑なクエリを構築されると意図しないSQLが生成される。

対策

  • RepositoryからはDTO or 最終形で返す、あるいは Expression<Func<>> を受け取る設計に。

📌 おまけ:ログで実行SQLを確認しよう

services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(...)
           .LogTo(Console.WriteLine, LogLevel.Information);
});

まとめ

原因 対策
不要な .Include() Select句で必要な項目だけ取得
早すぎる .ToList() 遅延評価のまま絞り込む
無駄なトラッキング .AsNoTracking() の活用
遅延読み込み N+1回避のため事前読み込み
長寿命なDbContext スコープを短く保つ
IQueryable濫用 RepositoryからはDTOで返す

EF Coreは正しく使えば非常に高速かつ便利なORMです。
「勝手に重くなる」ではなく、「知らずに重くしている」ことがほとんど。
この記事が、皆さんのEF Coreライフを少しでも快適にする一助になれば幸いです!

Discussion