Start with the response shape

A read endpoint usually needs a small projection, not an entire tracked entity graph. Select only the columns required by the response and keep filtering, ordering, and paging in the database.

var rows = await db.Orders
    .Where(order => order.Status == OrderStatus.Open)
    .OrderBy(order => order.CreatedAt)
    .Select(order => new OrderListItem(order.Id, order.Number, order.CreatedAt))
    .Take(pageSize)
    .ToListAsync(ct);

Recognize N+1 behavior

If a page loads 50 orders and then performs one query per order to read its customer, the request has an N+1 pattern. Prefer a projection that joins the needed customer fields. Include can be appropriate when the use case needs the related entity graph, but it should not be the default for a list response.

Bound the result

Apply the filter and stable ordering before paging. A page-size limit protects the database and the API. For multiple collections, compare a single joined query with split queries; joins can multiply rows, while split queries add round trips.

Verify the generated SQL

Inspect query logs or the generated SQL during development, then measure against realistic data. A passing LINQ expression is not proof that the database receives an efficient query. Avoid materializing early with ToList before filters that can be translated.

Interview takeaway

Explain the response contract first, show where projection occurs, identify how query count is bounded, and describe how you would inspect SQL and the execution plan.