EF Core'da N+1'i Profillemeden Görmek
Include her zaman dost değil, sessiz döngü daha sinsi

Belirti tanıdık: liste 20 satır, sayfa 1.2 saniye. CPU düşük, DB CPU yüksek. Debug log'da aynı SELECT 20 kez. Lazy loading açıktı; ToList'ten sonra foreach içinde navigation'a dokunuyorduk.
İlk yanlış: kör Include
Include(p => p.Author).ThenInclude(...) kartelayı tek sorguya çevirir, satırı da şişirir. 20 yazı × 8 etiket cartesian ürünü log'da tek SELECT olarak 'düzelmiş' görünür. Düzelmemiştir; payload büyümüştür.
var cards = await db.Posts.AsNoTracking()
.Where(p => p.Status == PostStatus.Published)
.OrderByDescending(p => p.PublishedAt)
.Select(p => new PostCard(
p.Id,
p.Title,
p.Slug,
p.Author.Name,
p.Tags.Select(t => t.Name).ToList()))
.Take(20)
.ToListAsync(ct);
Projection, Author ve Tags'i ihtiyaç kadar alır. Entity'yi materialise etmez. Bu, N+1'i kapatmanın en sakin yolu. Include'a ancak entity'yi gerçekten güncelleyeceksem bakıyorum.
AsSplitQuery ne zaman
Birden fazla collection Include etmek zorundaysam AsSplitQuery cartesian'ı böler. İki-üç SELECT, bir şişmiş SELECT'ten ucuz olabilir. 'Her zaman split' de ezber; küçük grafikte tek sorgu daha iyi.
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging(false);
}
Sensitive data production'da kapalı. Information log, SELECT sayısını gösterir. Daha iyisi: OpenTelemetry ile db.client.operation.count. 20 kart için 20 SELECT görürsem hissiyatla tartışmam, sayıyı düzeltirim.
Lazy loading'i suçlamak yetmez
Proxy'yi kapatmak N+1'i gizlemez, NullReference'a çevirir. Asıl disiplin: istek sınırında hangi kolonların gerektiğini yazmak. 'Belki lazım olur' ile Include yığmak, N+1'in ters yönlü kuzeni.
Yorumlar
Yorumlar (0)
Yorumlar üyelere açık. Üye ol · Giriş yap