즉시, 지연, 명시적 로딩
Include, ThenInclude, 지연 로딩 프록시, 명시적 Load 호출을 사용해 관련 데이터가 로드되는 시점을 제어합니다.
즉시, 지연, 명시적 로딩은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C# Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
세 가지 로딩 전략
EF Core는 관련 데이터를 로드하는 세 가지 전략을 제공합니다. 즉시 로딩(쿼리와 함께 로드), 지연 로딩(접근할 때 로드), 명시적 로딩(필요할 때 로드)입니다. 적절한 전략을 선택하면 N+1 문제와 불필요한 데이터베이스 왕복을 피할 수 있습니다.
Include를 사용한 즉시 로딩
즉시 로딩은 Include()를 사용하여 같은 데이터베이스 조회에서 관련 데이터를 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는 탐색 그래프를 더 깊은 수준까지 연결합니다. 한 번의 쿼리로 여러 수준의 데이터를 로드할 수 있습니다.
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 함정)
지연 로딩은 편리하지만 반복문에서는 위험합니다. 각 접근마다 새로운 데이터베이스 조회가 실행되어 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를 위한 분할 쿼리
하나의 쿼리에서 여러 컬렉션을 로드하면 카테시안 폭발(행이 곱셈처럼 늘어나는 현상)이 발생합니다. 컬렉션별로 별도의 쿼리를 실행하고 메모리에서 결합하려면 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실전: 올바른 전략 선택
실제 개발에서는 다음 결정 가이드를 사용하십시오:
- 즉시 로딩 + Include: 관련 데이터가 항상 필요할 때, 목록 페이지
- 프로젝션: 읽기 전용 뷰, API 응답 — 가장 효율적
- 명시적 로딩: 앱 로직에 따라 조건부로 로드할 때
- 지연 로딩: 웹 앱에서는 피하고, 데이터 집합이 작은 데스크톱 또는 스크립팅 환경에서만 사용
빠른 확인
지연 로딩에서 N+1 문제란 무엇입니까?
복습: 로딩 전략
핵심 요점:
- 즉시 로딩(
Include): 한 번의 쿼리로 데이터를 로드 — 액세스 패턴을 예측할 수 있을 때 적합 - 필터링된 Include: 자식 데이터의 일부만 로드 — EF Core 5 이상
- 지연 로딩: 편리하지만 위험함 — 반복문에서는 피해야 함(N+1 문제)
- 명시적 로딩: 세밀하게 제어하면서 필요할 때 로드
Select를 사용한 프로젝션: 읽기 전용 시나리오에서 가장 효율적- 분할 쿼리: 여러 컬렉션을 Include할 때 카테시안 폭발 방지
자주 묻는 질문
“즉시, 지연, 명시적 로딩” 강의는 무료인가요?
네 — “즉시, 지연, 명시적 로딩” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“즉시, 지연, 명시적 로딩”에서 뭘 배우나요?
Include, ThenInclude, 지연 로딩 프록시, 명시적 Load 호출을 사용해 관련 데이터가 로드되는 시점을 제어합니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
C# Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“즉시, 지연, 명시적 로딩” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 즉시, 지연, 명시적 로딩
- 원시 SQL, 저장 프로시저와 보간
- 컴파일된 쿼리와 성능
- 전역 쿼리 필터와 소유 엔터티