Tracking Queries Are the Default
When you write context.Blogs.Where(b => b.Rating > 3).ToList(), EF Core runs the query, materializes each row into an entity, and registers every one of those entities with the ChangeTracker as Unchanged. This is what makes it possible to mutate a property afterward and have SaveChanges pick it up automatically. The convenience comes at a cost: EF has to keep an identity map (so the same primary key always resolves to the same in-memory instance) and an original-value snapshot for every row, which consumes memory and adds bookkeeping overhead proportional to the number of rows returned.
Cricket analogy: It's like a scorer logging every ball bowled in a Test match into the official book by default — useful if you need to review the innings later, but overkill if you only wanted today's final score.
AsNoTracking: Opting Out for Read-Only Scenarios
Calling .AsNoTracking() on a query tells EF Core to skip both the identity map and the original-value snapshot for the returned entities — it just materializes objects and hands them back untracked. This is the right default for read-only workloads: API GET endpoints, report generation, or any query whose results you're never going to pass back into SaveChanges. Because there's no snapshot bookkeeping, no-tracking queries are measurably faster and use less memory, especially when materializing large result sets or deeply nested Include graphs.
Cricket analogy: It's like a fan watching the match purely for entertainment rather than as an official scorer — you absorb the action without maintaining a ball-by-ball ledger you'll never need to reference again.
AsNoTrackingWithIdentityResolution
Plain AsNoTracking() has one gotcha: if the same row appears multiple times across a query result (common with Include on a one-to-many relationship), EF Core materializes a separate object instance for each occurrence instead of resolving them to one shared instance, since there's no identity map to deduplicate against. AsNoTrackingWithIdentityResolution() fixes this by keeping a temporary, weak-referenced identity map scoped only to that single query, so duplicate rows resolve to the same in-memory instance without the overhead of full change tracking or long-lived snapshots.
Cricket analogy: It's like a stadium selling separate tickets for the same seat number across different gate entrances unless someone cross-checks a master seating chart to make sure Seat 14B always refers to the same physical seat.
Choosing the Right Query Mode
The decision is straightforward once you frame it correctly: use a tracking query (the default) whenever you intend to modify the results and call SaveChanges — for example, loading an order to update its status. Use AsNoTracking() for anything read-only, which in most web applications is the majority of queries, especially GET endpoints and DTO projections. Reach for AsNoTrackingWithIdentityResolution() specifically when you need duplicate-free object graphs from a no-tracking Include query, such as returning a Category with its Products where each Product also references back to the same Category instance.
Cricket analogy: It's like a captain deciding whether to review a decision with DRS — you only spend the review (tracking overhead) when the outcome actually needs to change, not on every single delivery.
// Read-only endpoint: skip change tracking entirely
public async Task<List<BlogDto>> GetBlogsAsync()
{
return await _context.Blogs
.AsNoTracking()
.Select(b => new BlogDto { Id = b.BlogId, Url = b.Url })
.ToListAsync();
}
// Duplicate-free graph from a no-tracking Include query
var categories = await _context.Categories
.Include(c => c.Products)
.AsNoTrackingWithIdentityResolution()
.ToListAsync();
// Update scenario: keep the default tracking behavior
var order = await _context.Orders.FirstAsync(o => o.OrderId == id);
order.Status = OrderStatus.Shipped;
await _context.SaveChangesAsync();
Calling SaveChanges after mutating an entity returned from an AsNoTracking() query will silently do nothing — the entity was never registered with the ChangeTracker, so EF has no snapshot to diff against and generates no SQL.
- Tracking is EF Core's default: every materialized entity is registered with the ChangeTracker and identity map.
- AsNoTracking() skips the identity map and snapshot, giving faster, lower-memory results for read-only queries.
- Mutating an AsNoTracking() entity and calling SaveChanges has no effect — EF isn't watching that instance.
- AsNoTrackingWithIdentityResolution() adds a query-scoped identity map to deduplicate repeated rows in Include graphs without full tracking overhead.
- Use tracking queries for anything you plan to modify and persist via SaveChanges.
- Use AsNoTracking() for GET endpoints, reports, and DTO projections that never round-trip back to SaveChanges.
- Choose AsNoTrackingWithIdentityResolution() when a no-tracking Include query would otherwise produce duplicate object instances for the same row.
Practice what you learned
1. What does AsNoTracking() skip compared to a default tracking query?
2. What happens if you mutate a property on an entity returned by an AsNoTracking() query and then call SaveChanges?
3. Why would you use AsNoTrackingWithIdentityResolution() instead of plain AsNoTracking()?
4. Which scenario is the best fit for a tracking (default) query?
5. How can you set no-tracking as the default behavior for an entire DbContext?
Was this page helpful?
You May Also Like
Change Tracking Explained
How EF Core's ChangeTracker snapshots entity state and computes the minimal set of INSERT, UPDATE, and DELETE statements needed at SaveChanges time.
Avoiding the N+1 Query Problem
How lazy loading silently causes one query per entity in a loop, and how Include, projection, and split queries fix it in EF Core.
Optimistic Concurrency Control
How EF Core detects and resolves concurrent edit conflicts using concurrency tokens and DbUpdateConcurrencyException, and how this compares to pessimistic locking.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics