Last updated: Feb 19, 2025

The N+1 Problem in Entity Framework Core: A Detective's Guide to Spotting and Fixing It

Written by Paul Gradie · 8 minutes read

The N+1 Problem in Entity Framework Core: A Detective's Guide to Spotting and Fixing It

In this post, Paul Gradie reveals how the notorious N+1 problem in EF Core—where seemingly simple data retrieval turns into a series of inefficient database trips—can be the hidden culprit behind performance woes. He breaks down how lazy loading and loops can lead to unnecessary queries, and shares actionable fixes to streamline your code. Dive in to uncover tips that will help you optimize your data access and keep your app running smoothly!

You’ve probably heard of the N+1 problem. Maybe someone mumbled it during a code review. Maybe it came up when your team was debugging a mysteriously sluggish app. Or, if you’re like me, you might have just nodded confidently and made a mental note to Google it later. Whatever the case, if you’re working with Entity Framework Core (EF Core) and relational data, the N+1 problem is a lurking performance killer.

Let’s unravel it together.


What ***Is***** the N+1 Problem?**

In summary - it is database lookup problem where your code is making far too many trips back and forth to the store to retrieve records one at a time instead of returning them all at once.

Imagine you’re hosting a pizza party for your friends. You call up your favorite pizza place and order 10 pizzas - one for each friend.

The delivery service brings you the 10 pizzas, but here’s the catch: they only bring the crust and sauce. No cheese, no toppings. They promise to bring the toppings separately. So, one by one, they return, each time bringing just a single topping for one pizza. They do this 10 times—once for each pizza.

By the time the last pizza finally gets its toppings, your friends are starving and frustrated. The pizza party has turned into a disaster, and you’re wondering why you didn’t just order the whole pizza, toppings and all, in a single trip.

This is the N+1 problem in EF Core. It happens when your application makes 1 query to fetch a list of entities (e.g., 10 pizzas) and then N additional queries to fetch related data for each item in that list (e.g., the toppings for each pizza). Instead of efficiently retrieving everything in one go, the app repeatedly fetches extra data in individual queries, causing delays and inefficiency.


How Does It Happen in Code?

The N+1 problem usually sneaks in when lazy loading or bad loops interact with EF Core. Here are two common ways this can manifest:

1. Lazy Loading

By default, EF Core doesn’t fetch related data unless you ask for it. But when lazy loading is enabled, it sneaks off to the database and grabs related data when you access a navigation property.

var books = context.Books.ToList();

foreach (var book in books)
{
    // Triggers a database query for every book
    Console.WriteLine(book.Chapters.Count);
}

Here, EF Core fetches all the Books in one query, but when we access book.Chapters, it fires another query for each book. So for 100 books, you’ve just made 101 trips to the database.

2. Explicit Queries in Loops

Even without lazy loading, looping over entities and running queries for each one can mimic the same problem.

var books = context.Books.ToList();

foreach (var book in books)
{
    // Another query per book
    var chapters = context.Chapters.Where(c => c.BookId == book.Id).ToList();
}

This isn’t EF Core’s fault—this is you manually recreating an N+1 scenario. Bummer!


How to Spot the N+1 Problem

1. Use Query Logs

EF Core can log every SQL query it generates. Turn on logging and run your suspicious code. If you see one query for the main entity and then N queries for related entities, you’ve got a problem.

optionsBuilder.LogTo(Console.WriteLine);

2. Look for Loops with Queries

Scan your codebase for patterns like this:

foreach (var entity in entities)
{
    var related = context.RelatedEntities.Where(...).ToList();
}

These are screaming “N+1” louder than a pager going off at 3 a.m.

3. Analyze Performance

N+1 problems aren’t always obvious with small datasets. But when your database grows, they make your app grind to a halt. If adding more data causes exponential performance degradation, it’s time to investigate.


Fixing the N+1 Problem

1. Eager Loading

Tell EF Core to grab related data upfront using .Include(). This ensures everything is fetched in one trip.

var books = context.Books.Include(b => b.Chapters).ToList();

No more extra queries when you access book.Chapters.

2. Projections

If you don’t need the full entities, project the data into a lightweight structure (like a DTO).

var books = context.Books
    .Include(b => b.Chapters)  // Eagerly load the related Chapters
    .Select(b => new
    {
        b.Title,
        // Project the data to get just the chapter titles
        Chapters = b.Chapters.Select(c => c.Title).ToList()
    })
    .ToList();

This avoids loading unnecessary properties and keeps your queries efficient.

3. Disable Lazy Loading (If possible)

Lazy loading can be a convenient way to automatically retrieve related data on request, but as we’ve seen, it can also cause performance issues if misused. Some code bases may have the option to disable lazy loading entirely if it is becoming a main source of pain - forcing everyone to be explicit about what data they’re fetching.

optionsBuilder.UseLazyLoadingProxies(false);

Lazy loading, however, can be a deliberate decision (as it is at Empower) to improve dev velocity. In such cases, ensuring teams are aware of this behavior and are deliberate about its usage is this the go. These sorts of decisions should be carefully considered, keeping in mind the needs and capabilities of the engineers involved.


How to Teach Your Team to Spot and Avoid N+1

1. Use Real Examples

Take a piece of your existing code with an N+1 issue. Log the queries and show your team how the SQL explodes. Then, fix it together and show the performance improvement.

2. Create a Checklist

Share this mental checklist:

  • Are you using **.Include()** or **.Select()** for related data?
  • Are there queries inside loops?
  • Does the query log show more than you expect?

3. Hands-On Exercises

Give them exercises where they deliberately create an N+1 problem. Then ask them to refactor it. Let them feel the pain—and then the joy of fixing it.

4. Code Reviews

Make query patterns part of your code review checklist. If you see loops with queries or unexplained lazy loading, ask questions.


Conclusion

The N+1 problem isn’t some abstract academic concern. It’s a real-world issue that can silently cripple your app. But the good news is it’s not hard to spot - and it’s even easier to fix once you know how. You’ll save yourself hours of debugging, speed up your app, and maybe even get that 3 a.m. pager to stay quiet by teaching your team to think critically about how EF Core fetches data.

And remember: Always grab the toppings with the pizza.


About Paul Gradie

Paul Gradie

_Paul is a software engineering leader / data scientist / biologist / team player / father with published contributions in the fields of reproductive biology, artificial intelligence, and software engineering. _

*Paul enjoys pair programming, learning, and building things well and is often told that he is personable, approachable, and enjoyable to work with 😀. He also maintains *Sailfish_ (a performance testing library for C#), and endeavors on / contributes to various side projects to hone his skills._

Connect with Paul

Keep reading

More in Engineering