Interview question
Refactor an N plus one order-status loop
Replaces a parent query plus one status query per row with one server-side projection.
TL;DR
Replaces a parent query plus one status query per row with one server-side projection.
N plus one diagnosis, latest-child projection, bounded query count, tie-breaking, and generated SQL inspection.
Practice the problem like a real interview: restate, reason, implement, and test.
The current endpoint loads 50 orders, then calls OrderStatusHistory.Where(...).OrderByDescending(...).FirstAsync() for each order. Return OrderStatusItem(OrderId, LatestStatus, ChangedAtUtc) in one database round trip. Status ties are resolved by history id.
Next in EF Core Labs: Composable Filters
A 50-order page must execute one query rather than 51. Each DTO still contains the complete latest status values from one winning history row.
I move the latest-status subquery into the projection while the whole expression is still translatable. I shape the latest child once, ordered by timestamp and id, and project it into the DTO. The provider can translate that into a lateral join or correlated subquery rather than issuing commands from an application loop.
public static Task<List<OrderStatusItem>> LoadOrderStatusesAsync(
AppDbContext db,
int pageSize,
CancellationToken cancellationToken)
{
pageSize = Math.Clamp(pageSize, 1, 100);
return db.Orders
.AsNoTracking()
.OrderByDescending(order => order.CreatedAtUtc)
.ThenByDescending(order => order.Id)
.Take(pageSize)
.Select(order => new
{
order.Id,
Latest = order.StatusHistory
.OrderByDescending(status => status.ChangedAtUtc)
.ThenByDescending(status => status.Id)
.Select(status => new
{
status.Status,
status.ChangedAtUtc
})
.FirstOrDefault()
})
.Select(row => new OrderStatusItem(
row.Id,
row.Latest == null ? null : row.Latest.Status,
row.Latest == null ? null : row.Latest.ChangedAtUtc))
.ToListAsync(cancellationToken);
}
The expected shape is one SQL command for one page. Exact SQL varies by provider, so command logging or an interceptor should verify the round-trip count. An index on status history beginning with order id and descending change order supports the latest-child lookup.
Include and huge payload.Spot a weak answer, missing edge case, or clearer explanation? Send it in.