What is the N+1 query problem and how do you solve it?
4 minintermediateEF-CoreN+1performancequery
Quick Answer
The N+1 problem is firing one query to load N parent rows, then N more queries to load each parent's related data (often from lazy loading or per-item access). It causes excessive round-trips and poor performance. Fix it with eager loading (`Include`), projection (`Select`) to fetch only needed data in one query, or batching, so related data loads in a single round-trip.
Detailed Answer
N+1 Query Problem occurs when you execute 1 query to fetch N records, then N additional queries to fetch related data for each record.
Example of N+1 Problem:
// 1 query to get orders
var orders = context.Orders.ToList();
// N queries (one per order) to get customers
foreach (var order in orders)
{
Console.WriteLine(order.Customer.Name); // Lazy loading triggers query
}
// Total: 1 + N queries!
Solutions:
1. Eager Loading with Include()
// Single query with JOIN
var orders = context.Orders
.Include(o => o.Customer)
.ToList();
foreach (var order in orders)
{
Console.WriteLine(order.Customer.Name); // No additional query
}
2. Select/Projection
// Query only needed data
var orderSummaries = context.Orders
.Select(o => new
{
OrderId = o.Id,
CustomerName = o.Customer.Name,
Total = o.Total
})
.ToList();
3. Split Queries for Multiple Includes
// Instead of one large JOIN, use multiple queries
var orders = context.Orders
.Include(o => o.Customer)
.Include(o => o.OrderItems)
.AsSplitQuery() // EF Core 5.0+
.ToList();
4. Explicit Loading with Batch
var orders = context.Orders.ToList();
// Load all customers in one query
var customerIds = orders.Select(o => o.CustomerId).Distinct();
var customers = context.Customers
.Where(c => customerIds.Contains(c.Id))
.ToDictionary(c => c.Id);
// Map in memory
foreach (var order in orders)
{
order.Customer = customers[order.CustomerId];
}
5. Disable Lazy Loading
// Don't enable lazy loading proxies
services.AddDbContext(options =>
options.UseSqlServer(connectionString)
// Don't use: .UseLazyLoadingProxies()
);
Detection Tools:
// Enable sensitive data logging and detailed errors
optionsBuilder
.EnableSensitiveDataLogging()
.EnableDetailedErrors()
.LogTo(Console.WriteLine, LogLevel.Information);