Back to Blog
๐Ÿ“Š
.NET CoreEF CoreSQL ServerPerformance

Entity Framework Core 8: Performance Optimization from N+1 to Production

5 Mar 202510 min readยทVenkatraman Nagarajan

The N+1 Problem

This is the most common EF Core performance killer. You load a list of orders, then for each order EF issues another query to load its items โ€” 1 query becomes N+1.

// BAD โ€” N+1 queries
var orders = await _db.Orders.ToListAsync();
foreach (var order in orders)
{
    // triggers a new query per order!
    Console.WriteLine(order.Items.Count);
}

// GOOD โ€” single join query
var orders = await _db.Orders
    .Include(o => o.Items)
    .ToListAsync();

AsNoTracking for Read Queries

The EF change tracker adds significant overhead for queries that will never be updated. On read-heavy endpoints, AsNoTracking() delivers a 20โ€“40% speed boost.

// API endpoint returning warehouse inventory
var items = await _db.WarehouseItems
    .AsNoTracking()
    .Where(i => i.LocationCode == locationCode)
    .Select(i => new ItemDto(i.Id, i.Sku, i.Quantity))
    .ToListAsync();

Compiled Queries

For hot paths that run thousands of times per minute, EF Core needs to translate LINQ to SQL each call. Compiled queries cache the translation:

// Compiled once, reused on every call
private static readonly Func<AppDbContext, string, Task<WarehouseItem?>>
    GetItemBySku = EF.CompileAsyncQuery(
        (AppDbContext db, string sku) =>
            db.WarehouseItems.FirstOrDefault(i => i.Sku == sku));

// Usage โ€” no LINQ translation overhead
var item = await GetItemBySku(_db, "SKU-12345");

Pagination โ€” Never Skip Without a Keyset

OFFSET/FETCH pagination gets slower as pages increase. With 1M rows, page 50,000 is painful. Switch to keyset pagination:

// SLOW for large pages
var page = await _db.Orders
    .OrderBy(o => o.CreatedAt)
    .Skip(pageIndex * pageSize)
    .Take(pageSize)
    .ToListAsync();

// FAST โ€” keyset cursor
var page = await _db.Orders
    .Where(o => o.CreatedAt > lastSeenDate)
    .OrderBy(o => o.CreatedAt)
    .Take(pageSize)
    .ToListAsync();

Profiling with EF Core Logging

Always profile in staging before shipping. Enable SQL logging to see exactly what queries EF generates:

// appsettings.Development.json
{
  "Logging": {
    "LogLevel": {
      "Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  }
}

Better yet, use MiniProfiler or Azure Application Insights to capture slow queries in production. A query taking >100ms on a hot path is a bug, not a warning.

Found this useful? Share it:

ยฉ 2026 VENKATRAMAN NAGARAJAN. All rights reserved.

Senior Full Stack Engineer ยท Chennai, India