AsNoTracking in EF Core

What is AsNoTracking?

AsNoTracking disables change tracking for a query, improving performance for read-only scenarios where you don’t need to update the retrieved entities.

Basic Usage

// With tracking (default)
var products = await context.Products.ToListAsync();

// Without tracking
var products = await context.Products
    .AsNoTracking()
    .ToListAsync();

Performance Benefits

// Benchmark comparison
// With tracking: ~100ms, 50MB memory
var tracked = await context.Products.ToListAsync();

// Without tracking: ~60ms, 30MB memory
var untracked = await context.Products
    .AsNoTracking()
    .ToListAsync();

When to Use AsNoTracking

Read-Only Queries

// API endpoints returning data
public async Task<IActionResult> GetProducts()
{
    var products = await context.Products
        .AsNoTracking()
        .ToListAsync();
    
    return Ok(products);
}

Reporting Queries

var report = await context.Orders
    .AsNoTracking()
    .Include(o => o.OrderItems)
    .Where(o => o.OrderDate >= startDate)
    .Select(o => new OrderReport
    {
        OrderId = o.Id,
        TotalAmount = o.TotalAmount,
        ItemCount = o.OrderItems.Count
    })
    .ToListAsync();

Large Dataset Queries

var allProducts = await context.Products
    .AsNoTracking()
    .ToListAsync(); // Better performance for large datasets

AsNoTrackingWithIdentityResolution

// Resolves duplicate entities in result set
var orders = await context.Orders
    .AsNoTrackingWithIdentityResolution()
    .Include(o => o.Customer)
    .Include(o => o.OrderItems)
        .ThenInclude(oi => oi.Product)
    .ToListAsync();

Global Configuration

// Set default tracking behavior
services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString)
           .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));

// Override for specific query
var products = await context.Products
    .AsTracking()
    .ToListAsync();

Limitations

// Cannot update untracked entities
var product = await context.Products
    .AsNoTracking()
    .FirstAsync(p => p.Id == 1);

product.Price = 99.99m;
await context.SaveChangesAsync(); // No update - entity not tracked

// Need to attach and set state
context.Products.Attach(product);
context.Entry(product).State = EntityState.Modified;
await context.SaveChangesAsync(); // Now updates

Best Practices

// 1. Use for read-only scenarios
var products = await context.Products
    .AsNoTracking()
    .ToListAsync();

// 2. Use with projections
var productDtos = await context.Products
    .AsNoTracking()
    .Select(p => new ProductDto
    {
        Id = p.Id,
        Name = p.Name,
        Price = p.Price
    })
    .ToListAsync();

// 3. Don't use when you need to update
var product = await context.Products
    .FirstAsync(p => p.Id == 1); // Keep tracking for updates
product.Price = 99.99m;
await context.SaveChangesAsync();

Performance Comparison

ScenarioWith TrackingAsNoTrackingImprovement
1000 entities100ms60ms40% faster
Memory usage50MB30MB40% less
10000 entities1000ms500ms50% faster

Summary

AsNoTracking improves performance by disabling change tracking for read-only queries. Use it for API responses, reports, and large datasets. Don’t use it when you need to update entities.

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.

Test Your Efcore Knowledge

Ready to put your skills to the test? Take our interactive Efcore quiz and get instant feedback on your answers.