컴파일된 쿼리와 성능
EF.CompileQuery, AsNoTracking, Select를 사용한 프로젝션으로 쿼리 성능을 높입니다.
컴파일된 쿼리와 성능은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C# Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
EF Core 쿼리 컴파일
EF Core가 LINQ 쿼리를 실행할 때마다 식 트리를 구문 분석하고, SQL로 변환한 다음, 유효성을 검사해야 합니다. 자주 실행되는 경로에서는 이러한 오버헤드가 누적됩니다. EF Core는 이를 최소화할 수 있는 여러 도구를 제공합니다.
읽기 전용 쿼리를 위한 AsNoTracking
AsNoTracking()은 변경 추적기 등록을 건너뜁니다. 읽기 전용 쿼리(API 응답, 보고서)에서는 쉽고 효과가 가장 큰 최적화로, 일반적으로 2~5배 더 빠르고 메모리 사용량도 적습니다.
// With tracking (default) — for entities you'll modify
var tracked = await _db.Products.FirstOrDefaultAsync(p => p.Id == 1);
// Without tracking — for read-only
var dto = await _db.Products
.AsNoTracking()
.Where(p => p.IsActive)
.Select(p => new ProductDto { Id = p.Id, Name = p.Name })
.ToListAsync();컴파일된 쿼리
EF.CompileAsyncQuery는 LINQ 쿼리를 대리자로 미리 컴파일합니다. 변환은 시작 시 한 번만 수행되며, 이후 호출에서는 컴파일 단계를 완전히 건너뜁니다.
// Define once, reuse many times
private static readonly Func<AppDbContext, int, Task<Product?>> GetProductById =
EF.CompileAsyncQuery(
(AppDbContext db, int id) =>
db.Products.AsNoTracking().FirstOrDefault(p => p.Id == id));
// Usage
var product = await GetProductById(_db, 42);성능을 위한 Select 프로젝션
항상 필요한 필드만 정확히 프로젝션하십시오. 전체 엔터티 대신 DTO를 선택하면 가져오는 열, 네트워크 데이터 양, 가비지 컬렉션 부담이 줄어듭니다.
// Load entire entity (over-fetch)
var all = await _db.Products.ToListAsync();
// Projection: only fetch needed columns
var names = await _db.Products
.AsNoTracking()
.Select(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
// SQL: SELECT Id, Name, Price FROM Products인덱스 힌트와 원시 쿼리 최적화
EF Core는 HasIndex()로 구성한 데이터베이스 인덱스를 활용합니다. 복합 인덱스, 필터링된 인덱스, 포함 열은 모두 쿼리 계획의 성능에 영향을 줍니다.
// Configure compound index in OnModelCreating
modelBuilder.Entity<Order>()
.HasIndex(o => new { o.CustomerId, o.Status })
.HasFilter("[Status] = 0") // filtered index
.IncludeProperties(o => o.PlacedAt);
// The query below will use this index efficiently
var pending = await _db.Orders
.AsNoTracking()
.Where(o => o.CustomerId == 5 && o.Status == OrderStatus.Pending)
.ToListAsync();Skip과 Take을 사용한 페이지 매김
결과 집합이 크면 항상 페이지 매김을 적용하십시오. Skip과 Take은 SQL의 OFFSET/FETCH로 변환되어 수천 개의 행을 메모리에 로드하지 않도록 합니다.
int page = 2, pageSize = 25;
var products = await _db.Products
.AsNoTracking()
.Where(p => p.IsActive)
.OrderBy(p => p.Name)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
// SQL: ... ORDER BY Name OFFSET 25 ROWS FETCH NEXT 25 ROWS ONLY효율적인 개수 세기
개수를 세기 위해 레코드를 로드하는 대신 CountAsync() 또는 AnyAsync()를 사용하십시오. 존재 여부만 확인하면 AnyAsync()가 COUNT보다도 빠릅니다.
// Total count for pagination
int totalCount = await _db.Products
.Where(p => p.IsActive)
.CountAsync();
// Existence check — faster than Count > 0
bool hasExpensive = await _db.Products
.AnyAsync(p => p.Price > 1000m);
// SQL: IF EXISTS (SELECT 1 FROM ...) SELECT 1AddRange를 사용한 일괄 삽입
AddRange를 사용하여 여러 엔터티를 한 번에 추가하십시오. EF Core는 INSERT 문을 더 적은 왕복으로 일괄 처리하며, 일괄 크기는 공급자에서 MaxBatchSize로 제어합니다.
var newProducts = Enumerable.Range(1, 1000)
.Select(i => new Product { Name = $"Product {i}", Price = i * 1.5m })
.ToList();
_db.Products.AddRange(newProducts);
await _db.SaveChangesAsync();
// Batched: typically 100-1000 rows per round-trip데이터베이스 함수에 EF.Functions 사용
EF.Functions는 데이터베이스별 함수(LIKE, 날짜 부분, 전문 검색)를 노출합니다. EF Core는 이러한 함수를 메모리에서 평가하지 않고 SQL로 변환합니다.
// LIKE pattern search (SQL-side)
var results = await _db.Products
.Where(p => EF.Functions.Like(p.Name, "%keyboard%"))
.AsNoTracking()
.ToListAsync();
// Date difference (SQL-side)
var recentOrders = await _db.Orders
.Where(o => EF.Functions.DateDiffDay(o.PlacedAt, DateTime.UtcNow) <= 7)
.ToListAsync();느린 쿼리 진단
EF Core 쿼리 로그 기록을 활성화하여 생성된 SQL을 확인하십시오. 개발 환경에서는 EnableSensitiveDataLogging()을 사용하여 매개 변수 값도 확인할 수 있습니다.
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlServer(connStr)
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging() // dev only!
.EnableDetailedErrors()); // dev only!실전: 최적화된 목록 엔드포인트
프로덕션에 바로 사용할 수 있는 목록 엔드포인트는 최고의 성능을 위해 AsNoTracking, 프로젝션, 인덱스, 페이지 매김을 함께 사용합니다.
app.MapGet("/products", async (
AppDbContext db,
string? search,
int page = 1, int size = 20) =>
{
var query = db.Products
.AsNoTracking()
.Where(p => p.IsActive);
if (!string.IsNullOrEmpty(search))
query = query.Where(p => EF.Functions.Like(p.Name, $"%{search}%"));
var total = await query.CountAsync();
var items = await query
.OrderBy(p => p.Name)
.Skip((page - 1) * size).Take(size)
.Select(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
return Results.Ok(new { total, items });
});빠른 확인
일반 LINQ 쿼리와 비교할 때 EF.CompileAsyncQuery의 주요 이점은 무엇입니까?
복습: 컴파일된 쿼리와 성능
핵심 요점:
AsNoTracking(): 읽기 전용 쿼리에서 변경 추적기를 건너뜀 — 쉽고 효과가 가장 큰 최적화EF.CompileAsyncQuery: 자주 실행되는 쿼리를 대리자로 미리 컴파일Select를 사용한 프로젝션: 필요한 열만 가져옴- 적절한 인덱스(
HasIndex): 쿼리 계획을 크게 개선 Skip/Take으로 페이지 매김을 적용하고, 존재 여부 확인에는AnyAsync사용- 개발 환경에서 쿼리 로그 기록을 활성화하여 느리거나 예상하지 못한 SQL을 발견
자주 묻는 질문
“컴파일된 쿼리와 성능” 강의는 무료인가요?
네 — “컴파일된 쿼리와 성능” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“컴파일된 쿼리와 성능”에서 뭘 배우나요?
EF.CompileQuery, AsNoTracking, Select를 사용한 프로젝션으로 쿼리 성능을 높입니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
C# Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“컴파일된 쿼리와 성능” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.