Your app works fine in development with 100 test records. Then production hits with real data, and suddenly everything crawls. Sound familiar?
I've debugged enough slow .NET apps to know the real culprit: bad EF Core queries. Here are the 11 most common mistakes I see in production code.
For detailed code examples and performance benchmarks, check out the full deep-dive guide.
1. The N+1 Query Problem
Bad: Load orders, then access order.Customer.Name
in a loop = 1,001 database calls
Fix: Use Include(o => o.Customer)
or projection with Select()
I've written a detailed breakdown of N+1 queries and Include vs SplitQuery if you want to dive deeper.
2. Premature ToList()
Bad: _context.Products.ToList().Where(p => p.Price > 100)
fetches everything first
Fix: Build your query completely, then call ToList()
once
3. Client-Side Evaluation
Bad: Using Func<T, bool>
forces filtering in memory after downloading all data
Fix: Use Expression<Func<T, bool>>
to keep filtering in SQL
4. Over-Fetching Data
Bad: Loading entire entities when you only need Name and Price
Fix: Use projection: Select(p => new { p.Name, p.Price })
5. Missing Indexes
Bad: Querying without indexes = full table scans
Fix: Add indexes for columns in WHERE, ORDER BY, and JOIN clauses
6. Unnecessary Change Tracking
Bad: EF Core tracks entities you'll never update, wasting memory
Fix: Use AsNoTracking()
for read-only queries
7. Lazy Loading Surprises
Bad: Accessing navigation properties triggers hidden database calls
Fix: Explicitly load related data with Include()
or turn lazy loading off
8. Poor DbContext Lifetime
Bad: Creating new contexts for every operation or keeping one alive forever
Fix: Use scoped lifetime in DI - one context per web request
9. Non-Compiled Repeated Queries
Bad: EF Core rebuilds query plans for identical queries
Fix: Use EF.CompileAsyncQuery()
for frequently executed queries
10. Individual Operations
Bad: Calling SaveChanges()
in a loop
Fix: Add all entities, then call SaveChanges()
once
11. No Performance Monitoring
Bad: Flying blind without seeing generated SQL
Fix: Enable query logging and use ToQueryString()
for debugging
The Real Cost
I've seen these mistakes turn 50ms queries into 5-second nightmares. One N+1 problem in production generated 2,000+ database calls for a single page load.
Quick Wins
- Add
AsNoTracking()
to read-only queries (instant memory savings) - Use projection instead of loading full entities
- Check your logs for repeated similar queries (N+1 red flag)
- Add indexes for your most common WHERE clauses
Debug Like a Pro
Enable query logging in development:
{
"Logging": {
"LogLevel": {
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}
Use ToQueryString()
to see what SQL EF Core generates before executing queries.
Bottom Line
Good EF Core performance isn't about tricks - it's about respecting what database operations actually cost. When you understand that every navigation property access might trigger a database call, you'll naturally write better code.
Most performance problems aren't in your business logic or UI. They're in those 5 lines of innocent-looking EF Core queries that seemed fine with test data.
Want the complete guide? I've written a detailed breakdown with full code examples, SQL output, and performance metrics at my post..
What EF Core performance issues have you run into? Drop a comment below.
Top comments (0)