If your EF Core application works perfectly with a small dataset but suddenly slows down as the amount of data grows, don't immediately blame your API, server, or database hardware.
The problem may be much simpler: your application is making too many database queries.
One of the most common causes is the N+1 query problem in EF Core.
The pattern is straightforward: the application executes one query to retrieve a collection of records and then makes additional queries to retrieve related data for each record.
For example, suppose an application loads 100 invoices and then retrieves customer information separately for each invoice. The result could be:
1 initial query + 100 additional queries = 101 database calls
The exact number depends on the number of records and the application's data-access pattern. The underlying problem is the same: too many database round trips for a single business operation.
What's tricky is that the C# code can look perfectly reasonable while the generated SQL tells a very different story.
In this article, we'll look at what causes N+1 queries in EF Core, how to detect and measure them, when to use Include(), Select(), or explicit loading, and why fewer queries isn't always the same as better performance.
What Is the N+1 Query Problem in EF Core?
The N+1 problem happens when an application executes:
One query to retrieve a collection of records
N additional queries to retrieve related data for each record
That's where the name comes from:
1 + N = N+1 database queries
Consider a simple invoice and customer relationship:
public class Invoice
{
public int Id { get; set; }
public decimal Amount { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
Now imagine loading invoices:
var invoices = await dbContext.Invoices.ToListAsync();
The application may execute one query to retrieve the invoices.
The problem can appear later when related data is accessed repeatedly:
foreach (var invoice in invoices)
{
Console.WriteLine(invoice.Customer.Name);
}
If lazy loading is enabled and configured, accessing invoice.Customer can trigger an additional database query for each invoice.
For 100 invoices, that could result in:
1 query for invoices
+
100 queries for customers
=
101 database calls
The important point is that EF Core does not automatically create an N+1 problem simply because navigation properties exist. The additional queries depend on the application's data-loading strategy and how related data is accessed.
Why Is N+1 a Problem?
At first, an extra database query might not seem significant.
If you have five records, five additional queries may not create an obvious performance problem.
But applications rarely stay at five records.
As data volume, concurrent users, and request frequency increase, the cost of those additional database round trips can become significant.
1. Higher API Latency
Every database round trip introduces overhead.
When a request results in dozens or hundreds of database calls, the cumulative latency can become much larger than expected.
An API that responds quickly with 10 records may behave very differently with 1,000 records.
2. Increased Database Workload
The database has to parse, execute, and return results for every query.
Even if each individual query is fast, hundreds or thousands of repeated queries can create unnecessary database workload.
3. Higher Infrastructure Costs
More database activity can mean:
Higher CPU utilization
Increased connection pressure
More database capacity
Additional cloud resources
Higher infrastructure costs
This becomes particularly important for applications running on cloud infrastructure where database resources scale with workload.
4. Scaling Problems
N+1 issues often become more visible as an application grows.
For example:
Small dataset
10 records
↓
11 database calls
Larger dataset
500 records
↓
501 database calls
Production workload
5,000 records
↓
5,001 database calls
The application may appear perfectly healthy during development while struggling under production-scale data.
5. SLA and User Experience Risk
Slow database access eventually becomes slow application behavior.
For customer-facing systems, this can affect:
API response times
Page load performance
Background jobs
Batch processing
User experience
SLA/SLO targets
That's why N+1 isn't simply a coding-style issue. It can become a production performance problem.
Why N+1 Problems Often Go Unnoticed
One of the biggest reasons N+1 problems survive development is that the application can appear to work correctly.
Suppose a developer tests an invoice page with 10 records.
Everything looks fine.
The same application might behave very differently when production contains:
10,000 invoices
Thousands of customers
Multiple concurrent users
Large reporting workloads
Background processes running simultaneously
Functional testing answers an important question:
Does the application return the correct result?
Performance testing needs to answer a different question:
How much database work does the application perform to produce that result?
Those are not the same thing.
Does EF Core Always Cause N+1 Queries?
No.
EF Core does not automatically generate an N+1 query every time you access a navigation property.
N+1 behavior typically appears because of the application's data-loading pattern.
Common causes include:
Lazy loading
Accessing navigation properties inside loops
Loading related entities individually
Repeated repository calls
Data-access logic hidden inside helper methods
Queries that retrieve incomplete data and then fetch more data repeatedly
For example:
var invoices = await dbContext.Invoices.ToListAsync();
foreach (var invoice in invoices)
{
var customerName = invoice.Customer.Name;
}
This pattern becomes problematic when Customer is lazy-loaded.
The C# code looks simple, but the database activity may look more like:
SELECT ... FROM Invoices
SELECT ... FROM Customers WHERE Id = 1
SELECT ... FROM Customers WHERE Id = 2
SELECT ... FROM Customers WHERE Id = 3
...
The database sees many separate operations even though the application code appears to perform one simple business operation.
How to Detect N+1 Queries in EF Core
The first step toward fixing N+1 is finding out what your application is actually sending to the database.
Don't guess.
Measure it.
1. Enable EF Core SQL Logging
EF Core can log generated SQL queries.
For example:
builder.Services.AddDbContext<AppDbContext>(options =>
{
options
.UseSqlServer(connectionString)
.LogTo(Console.WriteLine, LogLevel.Information);
});
When the application runs, you can inspect the SQL being generated.
If one request produces:
SELECT ... FROM Invoices
SELECT ... FROM Customers WHERE Id = 1
SELECT ... FROM Customers WHERE Id = 2
SELECT ... FROM Customers WHERE Id = 3
SELECT ... FROM Customers WHERE Id = 4
...
you have a strong indication that related data is being loaded repeatedly.
2. Count Database Queries
Another useful technique is to measure how many database queries are executed for a particular operation.
For example, you might establish an expectation such as:
Invoice list request
Expected database queries: 1–3
Actual database queries: 101
The difference immediately highlights a potential problem.
Query-count monitoring can be particularly useful in integration or performance tests.
3. Inspect Generated SQL
Don't only count queries.
Look at the SQL itself.
Two implementations might both execute one query while producing very different SQL and returning very different amounts of data.
This is why database performance analysis should consider:
Number of queries
Query execution time
Rows returned
Columns returned
Joins
Index usage
Database CPU
Memory consumption

Join the conversation! Your thoughts help the community grow.