Loading...
Loading...
Optimize Entity Framework Core queries by fixing N+1 problems, choosing correct tracking modes, using compiled queries, and avoiding common performance traps. Use when EF Core queries are slow, generating excessive SQL, or causing high database load.
npx skill4agent add dotnet/skills optimizing-ef-core-queries| Input | Required | Description |
|---|---|---|
| Slow EF Core queries | Yes | The LINQ queries or DbContext usage to optimize |
| SQL output or logs | No | EF Core generated SQL or query execution logs |
// In Program.cs or DbContext configuration:
optionsBuilder
.UseSqlServer(connectionString)
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging() // shows parameter values (dev only!)
.EnableDetailedErrors();Microsoft.EntityFrameworkCore{
"Logging": {
"LogLevel": {
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}var orders = await db.Orders.ToListAsync();
foreach (var order in orders)
{
// Each access triggers a lazy-load query!
var items = order.Items.Count;
}// Option 1: Include (JOIN)
var orders = await db.Orders
.Include(o => o.Items)
.ToListAsync();
// Option 2: Split query (separate SQL, avoids cartesian explosion)
var orders = await db.Orders
.Include(o => o.Items)
.AsSplitQuery()
.ToListAsync();
// Option 3: Explicit projection (best - only fetches needed columns)
var orderSummaries = await db.Orders
.Select(o => new OrderSummary
{
OrderId = o.Id,
Total = o.Items.Sum(i => i.Price),
ItemCount = o.Items.Count
})
.ToListAsync();| Scenario | Use |
|---|---|
| 1 level of Include | Single query (default) |
| Multiple Includes (Cartesian risk) | |
| Include with large child collections | |
| Need transaction consistency | Single query |
// Per-query
var products = await db.Products
.AsNoTracking()
.Where(p => p.IsActive)
.ToListAsync();
// Global default for read-heavy apps
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));AsNoTrackingWithIdentityResolution()// Define once as static
private static readonly Func<AppDbContext, int, Task<Order?>> GetOrderById =
EF.CompileAsyncQuery((AppDbContext db, int id) =>
db.Orders
.Include(o => o.Items)
.FirstOrDefault(o => o.Id == id));
// Use repeatedly — skips query compilation overhead
var order = await GetOrderById(db, orderId);| Trap | Problem | Fix |
|---|---|---|
| Loads entire table into memory | Filter first: |
| Scans all rows | Use |
| Include is ignored with projection | Remove Include, use Select only |
| May not translate, falls to client eval | Use |
Calling | Causes nested queries | Use projection with |
var results = await db.Orders
.FromSqlInterpolated($@"
SELECT o.* FROM Orders o
INNER JOIN (
SELECT OrderId, SUM(Price) as Total
FROM OrderItems
GROUP BY OrderId
HAVING SUM(Price) > {minTotal}
) t ON o.Id = t.OrderId")
.AsNoTracking()
.ToListAsync();AsNoTracking()| Pitfall | Solution |
|---|---|
| Lazy loading silently creating N+1 | Remove |
| Global query filters forgotten in perf analysis | Check |
| DbContext should be scoped (per-request); don't cache it |
| Batch updates fetching then saving | EF Core 7+: use |
String interpolation in | SQL injection risk — use |