DEV Community

Cover image for Entity Framework Core Isn’t Slow; You’re Just Using It Wrong
Odumosu Matthew
Odumosu Matthew

Posted on

Entity Framework Core Isn’t Slow; You’re Just Using It Wrong

EF Core is slow.
It’s one of the most common complaints I hear from developers and one of the most misleading.

After years of working with .NET and building high-scale, enterprise applications, I can say with confidence:
Entity Framework Core isn’t inherently slow, most of the time, it’s being misused.

Yes, EF Core has its limits. No ORM is perfect. But in the majority of projects I’ve audited or worked on, performance issues weren’t caused by EF itself — they were caused by how developers used EF.

In this article, I’ll walk through the most common mistakes that lead to poor EF Core performance, and show you practical solutions to get things running smoothly.

🚫 Mistake 1: Not Using AsNoTracking() for Read-Only Queries

By default, EF Core tracks all entities it retrieves from the database. That means it keeps a copy of every entity and monitors it for changes so that you can update it later.

That’s useful, but only when you actually need to update the data.

If you're just reading data (e.g., returning it from an API or showing it in a view), tracking is unnecessary and adds significant overhead.

🔧 The Fix: Use AsNoTracking()

// ❌ Slower: Tracking enabled
var products = await _context.Products.ToListAsync();

// ✅ Faster: Tracking disabled
var products = await _context.Products
    .AsNoTracking()
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

✅ When to Use

  • Report generation

  • Read-only admin panels

  • Any time you don’t call SaveChanges()

Using AsNoTracking() can dramatically reduce memory usage and speed up query execution, especially on large datasets.

🐌 Mistake 2: Relying on Lazy Loading - The N+1 Query Problem

Lazy loading sounds convenient, EF loads related entities only when you access them. But this can easily cause what’s known as the N+1 query problem.

🧨 Example: The Pitfall

var students = await _context.Students.ToListAsync();

foreach (var student in students)
{
    // Triggers a separate SQL query per student
    var courseName = student.EnrolledCourse.Name;
}

Enter fullscreen mode Exit fullscreen mode

This can destroy performance.

🔧 The Fix: Use Eager Loading with .Include()

var students = await _context.Students
    .Include(s => s.EnrolledCourse)
    .ToListAsync(); // Just 1 query

Enter fullscreen mode Exit fullscreen mode

✅ Best Practices

  • Use .Include() when you know you’ll need related data.

  • Avoid lazy loading unless you’re dealing with very small or optional relationships.

  • Disable lazy loading globally if it’s not required:

services.AddDbContext<AppDbContext>(options =>
    options.UseLazyLoadingProxies(false));

Enter fullscreen mode Exit fullscreen mode

🧼 Mistake 3: Keeping DbContextAlive Too Long

The DbContextclass is designed to be short-lived and used for a single unit of work. Holding onto it for too long (like across multiple requests or operations) can lead to:

  • Memory leaks

  • Excessive tracking

  • Concurrency issues

🔧 The Fix: Use Scoped Lifetime

In an ASP.NET Core app, always register your DbContextwith a scoped lifetime:

services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer("your-connection-string"),
    ServiceLifetime.Scoped);

Enter fullscreen mode Exit fullscreen mode

In short:

  • Don’t treat DbContext as a singleton.

  • Don’t inject it into background threads or async voids.

  • Do keep it scoped to the request or operation.

🎯 Mistake 4: Pulling Full Entities Instead of DTOs

One of the most common (and expensive) EF Core mistakes is loading entire entities from the database when you only need a few fields.

Let’s say you need to show a list of users with their name and email:

❌ Inefficient Query

var users = await _context.Users.ToListAsync();
// Loads all columns, including those you don't need

Enter fullscreen mode Exit fullscreen mode

✅ Efficient Query with Projection

var users = await _context.Users
    .Select(u => new UserDto
    {
        Id = u.Id,
        Name = u.Name,
        Email = u.Email
    })
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

This generates a lean SQL query that only selects the fields you need. It also skips navigation properties and lazy loading entirely.

Bonus: Use AutoMapper for Clean Projection

var users = await _context.Users
    .ProjectTo<UserDto>(_mapper.ConfigurationProvider)
    .ToListAsync();

Enter fullscreen mode Exit fullscreen mode

🔧 Bonus Tips for Senior Software Engineers
📦 Use Bulk Operations for Performance at Scale

EF Core does not optimize batch inserts, updates, or deletes by default.

Instead of:

foreach (var item in list)
{
    _context.Update(item);
}
await _context.SaveChangesAsync();

Enter fullscreen mode Exit fullscreen mode

Use a library like EFCore.BulkExtensions

await _context.BulkUpdateAsync(list);

Enter fullscreen mode Exit fullscreen mode

🕵️‍♂️ Monitor EF Core Queries with Logging

Add query logging to see exactly what SQL EF is executing:

services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer("your-connection-string")
           .LogTo(Console.WriteLine, LogLevel.Information));
Enter fullscreen mode Exit fullscreen mode

This helps you:

  • Spot N+1 problems

  • Detect large object graphs

  • Optimize complex LINQ expressions

📌 TL;DR — EF Core Performance Checklist

❌ Mistake ✅ Solution
Tracking unnecessary data Use AsNoTracking()
N+1 queries via lazy loading Use .Include()
Holding DbContext too long Use scoped DbContext
Fetching full entities Project into DTOs
Slow batch operations Use BulkExtensions
Hidden slow queries Enable SQL logging

🧠 Final Thoughts

EF Core is a powerful, flexible ORM. But like any powerful tool, it’s easy to misuse. Most performance issues I’ve seen in production applications come down to:

  • Not understanding how EF Core works under the hood

  • Relying on defaults instead of being intentional

  • Prioritizing convenience over performance

Once you learn how to use EF Core the right way, it becomes a fast, scalable, and elegant data access layer, even for complex, high-load applications.

🙋‍♂️ Over to You

Have you struggled with EF Core performance in the past? Have a tip I missed?

Let me know in the comments or connect with me directly. I’d love to hear your experience.

LinkedIn Account : LinkedIn
Twitter Account: Twitter
Credit: Graphics sourced from Gunnarpeipman Blog

Top comments (0)