ef-core-query-reviewlisted
Install: claude install-skill Sarmkadan/dotnet-senior-skills
# EF Core Query Review
## N+1: lazy loading and loops over navigations
```csharp
// WRONG: 1 query for orders + N queries for customers
var orders = await db.Orders.ToListAsync();
foreach (var o in orders) Console.WriteLine(o.Customer.Name); // lazy-load per row
```
Fix with a projection, not an Include, when you only need a few fields:
```csharp
var rows = await db.Orders
.Select(o => new { o.Id, CustomerName = o.Customer.Name })
.ToListAsync(); // single query, single roundtrip
```
If lazy-loading proxies are enabled project-wide, treat every navigation access outside the query as a suspect. Prefer disabling lazy loading entirely; it converts silent N+1 into a visible exception.
## Cartesian explosion with multiple collection Includes
```csharp
// WRONG: rows = orders x items x payments; 100 orders with 50 items and 10 payments = 50,000 rows
var o = await db.Orders.Include(x => x.Items).Include(x => x.Payments).ToListAsync();
```
Two or more collection `Include`s on the same level multiply row counts. Use `AsSplitQuery()` (one query per collection, consistent only inside a transaction or with snapshot isolation) or split into separate targeted queries. One collection Include is fine; two is a review comment; three is a rejection.
## Projection over materialization
Materializing full entities to return a DTO is the most common over-fetch. `Select` into the DTO directly: EF translates it to a column list, skips change tracking, and avoids loading unmapped bl