Entity Framework  

EF Core Tip: Improve Read Performance with AsNoTracking()

Introduction

In this article, we will learn about EF Core Tips to Improve Read Performance with AsNoTracking().

Before we start, please take a look at my last article on Entity Framework.

Now, let's get started.

When working with Entity Framework Core (EF Core), one of the easiest ways to boost performance for read-heavy operations is by using AsNoTracking(). This small change can make a big difference in applications like APIs, dashboards, and reporting systems.

🔍 The Problem: Default Tracking Adds Overhead

By default, EF Core tracks every entity it retrieves. This allows it to:

  • Detect changes

  • Automatically persist updates using SaveChanges()

However, this tracking comes at a cost:

  • Extra memory usage

  • Additional CPU overhead

  • Slower query performance for large datasets

👉 If you're only reading data, this tracking is unnecessary.

✅ The Solution: AsNoTracking()

AsNoTracking() tells EF Core:

“Fetch the data, but don’t track it in the DbContext.”

🧪 Example Scenario

Let's say you're building a Product API that returns a list of active products.

🔴 Without AsNoTracking()

public async Task<List<Product>> GetActiveProducts()
{
    return await _context.Products
        .Where(p => p.IsActive)
        .ToListAsync();
}

Issues

  • EF Core tracks all retrieved Product entities.

  • Unnecessary overhead if you're just displaying data.

🟢 With AsNoTracking()

public async Task<List<Product>> GetActiveProducts()
{
    return await _context.Products
        .AsNoTracking()
        .Where(p => p.IsActive)
        .ToListAsync();
}

✅ Benefits

  • Faster execution

  • Reduced memory consumption

  • Better scalability for high-load APIs

⚡ Real-World Use Case

Imagine a dashboard showing:

  • Recent orders

  • Customer lists

  • Sales reports

All of these are read-only views.

public async Task<List<OrderDto>> GetRecentOrders()
{
    return await _context.Orders
        .AsNoTracking()
        .OrderByDescending(o => o.CreatedDate)
        .Select(o => new OrderDto
        {
            Id = o.Id,
            CustomerName = o.Customer.Name,
            TotalAmount = o.TotalAmount
        })
        .ToListAsync();
}

👉 This avoids tracking thousands of rows unnecessarily and keeps your API responsive.

❗ Important Caveat

Entities retrieved using AsNoTracking() are not tracked, so changes won’t be saved automatically.

❌ This Will NOT Work

var product = await _context.Products
    .AsNoTracking()
    .FirstAsync(p => p.Id == 1);

product.Price = 100;
await _context.SaveChangesAsync(); // No update!

✅ Correct Approach

_context.Products.Update(product);
await _context.SaveChangesAsync();

—or fetch the entity without AsNoTracking() if you intend to update it.

🆚 When Should You Use It?

✅ Use AsNoTracking() When

  • Data is read-only

  • You're building GET APIs

  • Query returns large datasets

  • Performance optimization is critical

❌ Avoid When

  • You plan to update the entity

  • You rely on automatic change tracking

  • Complex graph updates are involved

💡 Pro Tip: Make No-Tracking the Default

If most of your queries are read-only:

optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);

You can still override per query:

context.Products.AsTracking().FirstOrDefaultAsync();

🧠 Advanced: Identity Resolution

AsNoTrackingWithIdentityResolution()

  • Avoids tracking

  • Ensures same entity instance is reused within query results

Useful For

  • Complex joins

  • Related entity graphs

✅ Summary

  • AsNoTracking() is a low-effort, high-impact performance optimization.

  • Ideal for read-only queries and APIs.

  • Reduces memory usage and execution time.

  • Avoid using it when updates are required.

🚀 Final Thought

If your application is read-heavy (which most modern apps are), start using AsNoTracking() consistently—it’s one of the simplest ways to scale EF Core efficiently.

Conclusion

In this article, I have tried to cover EF Core Tips to Improve Read Performance with AsNoTracking().