Eager、Lazy、Explicit Loading
Include、ThenInclude、遅延読み込みプロキシ、明示的なLoad呼び出しを使って、関連データを読み込むタイミングを制御します。
「Eager、Lazy、Explicit Loading」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
3 つの読み込み戦略
EF Core には、関連データを読み込む 3 つの戦略があります。イーガーローディング(クエリと同時に読み込む)、レイジーローディング(アクセス時に読み込む)、明示的な読み込み(必要に応じて読み込む)です。適切な方法を選ぶことで、N+1 問題と不要なデータベースへの往復を避けられます。
Include によるイーガーローディング
イーガーローディングでは、Include() を使用して関連データを同じ SQL クエリに JOIN します。最も予測しやすい戦略であり、N+1 問題を回避できます。
// Single query with JOIN
var blogs = await _db.Blogs
.Include(b => b.Posts)
.Where(b => b.IsActive)
.ToListAsync();
// SQL: SELECT * FROM Blogs LEFT JOIN Posts ON ...入れ子になったリレーションシップの ThenInclude
ThenInclude はナビゲーションのグラフをさらに深く辿ります。1 回のクエリで複数の階層を読み込めます。
var orders = await _db.Orders
.Include(o => o.Customer)
.Include(o => o.Lines)
.ThenInclude(l => l.Product)
.ThenInclude(p => p.Category)
.Where(o => o.Status == OrderStatus.Pending)
.ToListAsync();フィルター付き Include
EF Core 5 以降では、Include の中で関連データをフィルタリングできます。これにより、別のクエリを実行せずに子エンティティの一部だけを読み込めます。
// Load only published posts for each blog
var blogs = await _db.Blogs
.Include(b => b.Posts.Where(p => p.IsPublished)
.OrderByDescending(p => p.PublishedAt)
.Take(5))
.ToListAsync();レイジーローディングの設定
レイジーローディングでは、ナビゲーションプロパティに初めてアクセスしたときに、関連データが自動的に読み込まれます。有効にするには Microsoft.EntityFrameworkCore.Proxies パッケージをインストールし、UseLazyLoadingProxies() を呼び出します。
// 1. Install package: dotnet add package Microsoft.EntityFrameworkCore.Proxies
// 2. Enable in DbContext setup:
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlite(connStr)
.UseLazyLoadingProxies());
// 3. Mark navigation properties as virtual:
public class Blog
{
public int Id { get; set; }
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}レイジーローディングの動作(N+1 の罠)
レイジーローディングは便利ですが、ループ内では危険です。アクセスするたびに新しい SQL クエリが発行され、N+1 問題を引き起こすためです。一覧を扱う場合は、常にイーガーローディングを優先してください。
var blogs = await _db.Blogs.ToListAsync(); // 1 query
foreach (var blog in blogs)
{
// BAD: each access fires a new query!
var count = blog.Posts.Count; // N queries
Console.WriteLine(count);
}
// FIX: Use eager loading
var blogs2 = await _db.Blogs.Include(b => b.Posts).ToListAsync();明示的な読み込み
明示的な読み込みでは、Entry().Collection().LoadAsync() または Entry().Reference().LoadAsync() を使用して、必要なときにナビゲーションプロパティを読み込めます。関連データが条件付きで必要になる場合に便利です。
var blog = await _db.Blogs.FindAsync(1); // no posts loaded
// Load posts only when needed
if (needsPosts)
{
await _db.Entry(blog!)
.Collection(b => b.Posts)
.LoadAsync();
}
// Load single reference
var post = await _db.Posts.FindAsync(99);
await _db.Entry(post!)
.Reference(p => p.Blog)
.LoadAsync();クエリフィルターを使った明示的な読み込み
LoadAsync() を呼び出す前に .Query() を使うと、明示的な読み込みにフィルターを適用でき、返されるデータ量を減らせます。
var blog = await _db.Blogs.FindAsync(1);
// Only load posts published this year
await _db.Entry(blog!)
.Collection(b => b.Posts)
.Query()
.Where(p => p.PublishedAt.Year == DateTime.UtcNow.Year)
.LoadAsync();
Console.WriteLine(blog!.Posts.Count);大規模な Include のための分割クエリ
1つのクエリで複数のコレクションを読み込むと、カルテジアン爆発(行数の乗算)が発生します。AsSplitQuery()を使うと、コレクションごとに別々のクエリを発行し、メモリ上で結合できます。
var orders = await _db.Orders
.Include(o => o.Lines)
.Include(o => o.Shipments)
.AsSplitQuery() // 3 separate SELECTs instead of one giant JOIN
.ToListAsync();
// Or configure globally:
opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);過剰取得を避けるプロジェクション
すべての関連データを含む完全なエンティティを読み込む代わりに、SelectでDTOにプロジェクションしてください。必要なデータだけを、最適化された単一クエリで正確に取得できます。
var summaries = await _db.Blogs
.Select(b => new BlogSummaryDto
{
Name = b.Name,
PostCount = b.Posts.Count(p => p.IsPublished),
LatestPost = b.Posts
.Where(p => p.IsPublished)
.OrderByDescending(p => p.PublishedAt)
.Select(p => p.Title)
.FirstOrDefault()
})
.ToListAsync();
// No Include needed - translated to subqueries実践例:適切な戦略の選択
実際の開発では、次の判断ガイドを使い分けてください:
- Eager + Include:関連データを常に必要とする場合、一覧ページ
- Projection:読み取り専用ビュー、APIレスポンス — 最も効率的
- Explicit:アプリケーションのロジックに基づく条件付き読み込み
- Lazy:Webアプリでは避け、小規模なデータセットを扱うデスクトップアプリやスクリプトでのみ使用
確認問題
遅延読み込みにおけるN+1問題とは何ですか?
まとめ:読み込み戦略
要点:
- Eager loading(
Include):1つのクエリでデータを読み込む — 予測可能なアクセスパターンに最適 - Filtered Include:子要素の一部だけを読み込む — EF Core 5以降
- Lazy loading:便利ですがリスクがある — ループ内では避ける(N+1問題)
- Explicit loading:細かく制御しながら必要に応じて読み込む
SelectによるProjection:読み取り専用シナリオで最も効率的- Split queries:複数のコレクションをIncludeした際のカルテジアン爆発を回避
よくある質問
「Eager、Lazy、Explicit Loading」レッスンは無料ですか?
はい。「Eager、Lazy、Explicit Loading」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「Eager、Lazy、Explicit Loading」で何を学びますか?
Include、ThenInclude、遅延読み込みプロキシ、明示的なLoad呼び出しを使って、関連データを読み込むタイミングを制御します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。
「Eager、Lazy、Explicit Loading」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Eager、Lazy、Explicit Loading
- Raw SQL、ストアドプロシージャと補間
- コンパイル済みクエリとパフォーマンス
- グローバルクエリフィルターと所有エンティティ