🔥
Entity Frameworkが勝手に重くなる理由と対策
Entity Framework(EF Core)は、.NET開発者にとって非常に強力なORMですが、何も考えずに使うと「勝手に重くなる」ことがあります。
本記事では、EF Coreを使っているうちに気づかぬうちに発生しがちなパフォーマンス劣化の原因と、その具体的な対策を紹介します。
.Include()
の多用
✅ 原因1:不要な 問題点
- 必要以上にリレーション先を読み込むと、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();
.ToList()
の早すぎる呼び出し
✅ 原因2: 問題点
-
.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