Explain tracking vs no-tracking queries in EF Core.
4 minintermediateEF-Corechange-trackingperformance
Quick Answer
Tracking queries (the default) load entities into the change tracker so EF Core can detect modifications and persist them on `SaveChanges`. No-tracking queries (`AsNoTracking()`) skip this, returning lighter, faster results that can't be updated directly. Use tracking when you'll modify and save entities; use no-tracking for read-only reads to reduce memory and CPU overhead.
Detailed Answer
Tracking Queries (Default)
EF Core keeps track of entity changes in the change tracker for SaveChanges().
// Tracking enabled by default
var product = context.Products.First();
product.Price = 99.99m;
context.SaveChanges(); // Change tracked and saved
How Change Tracking Works:
var product = context.Products.Find(1);
// Check tracking state
var entry = context.Entry(product);
Console.WriteLine(entry.State); // EntityState.Unchanged
product.Name = "Updated";
Console.WriteLine(entry.State); // EntityState.Modified
// See what changed
foreach (var property in entry.Properties)
{
if (property.IsModified)
{
Console.WriteLine($"{property.Metadata.Name}: " +
$"{property.OriginalValue} -> {property.CurrentValue}");
}
}
No-Tracking Queries Entities are not tracked, improving performance for read-only scenarios.
// No-tracking for single query
var products = context.Products
.AsNoTracking()
.Where(p => p.IsActive)
.ToList();
// Changes are NOT tracked
products[0].Price = 99.99m;
context.SaveChanges(); // Nothing saved!
Global No-Tracking Configuration:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
// Override for specific query
var product = context.Products
.AsTracking()
.First();
AsNoTrackingWithIdentityResolution
// Maintains identity resolution within query without full tracking
var orders = context.Orders
.Include(o => o.Customer)
.Include(o => o.OrderItems)
.ThenInclude(oi => oi.Product)
.AsNoTrackingWithIdentityResolution() // Same customer instance reused
.ToList();
Comparison:
| Aspect | Tracking | No-Tracking |
|---|---|---|
| Performance | Slower (memory overhead) | 30-40% faster |
| Memory | Higher | Lower |
| Use Case | Update/Delete operations | Read-only queries |
| Identity Resolution | Automatic | Not by default |
| SaveChanges() | Detects changes | No changes detected |
When to Use Each:
Use Tracking:
- Updating or deleting entities
- Need automatic change detection
- Working with related entities
- Short-lived contexts
Use No-Tracking:
- Read-only queries
- Display/reporting
- API GET endpoints
- Large result sets
- Performance critical scenarios
Manual Tracking Control:
// Detach entity
context.Entry(product).State = EntityState.Detached;
// Attach and mark as modified
context.Attach(product);
context.Entry(product).State = EntityState.Modified;
// Track specific properties
context.Entry(product).Property(p => p.Price).IsModified = true;