Behavior of DbSet.Find() vs. LINQ Queries
When QueryTrackingBehavior.NoTracking is configured globally, standard LINQ queries (e.g., Where(), First()) return entities that are not tracked by the DbContext. However, DbSet.Find() (and DbSet.FindAsync()) behaves differently because it first checks the local tracker before querying the database.
Even with global no-tracking enabled, Find() will return a tracked entity if it is already present in the context's memory. If the entity is not in memory and is fetched from the database, it is still tracked by default. This is a critical distinction: Find() is designed for primary key lookups and maintains tracking to ensure identity resolution within the current unit of work, regardless of the global query setting.
Efficiently Re-attaching Entities for Updates
In a global no-tracking environment, the ChangeTracker does not know which properties have changed. To update a subset of entities without triggering unnecessary database round-trips or updating every single column, use the following strategies:
1. Explicit State Marking (Most Efficient)
If you know exactly which entity has changed, use the Update method or manually set the entity state. context.Update(entity) marks the entity as Modified, which tells EF Core to send an UPDATE statement for all properties.
// Marks all properties as modified
context.Update(userProfile);
await context.SaveChangesAsync();
2. Targeted Property Updates (Optimized)
To avoid updating every column (reducing payload size and trigger execution), attach the entity and mark only specific properties as modified. This prevents a full row overwrite.
// Attach the entity to the tracker without marking it Modified
context.Attach(userProfile);
// Mark only the changed property as dirty
context.Entry(userProfile).Property(x => x.Email).IsModified = true;
await context.SaveChangesAsync();
Comparison of Update Methods
| Method |
Tracking State |
DB Impact |
Update() |
Modified |
Updates all columns |
Attach() + IsModified |
Unchanged ➜ Modified |
Updates only specified columns |
AsTracking() query |
Tracked |
Updates only changed columns (via snapshot) |
Assumptions and Verification
These behaviors assume EF Core 3.0+ where QueryTrackingBehavior was introduced. To verify your current state management:
- Verify Tracking: Check
context.ChangeTracker.Entries().Count() after a LINQ query; it should be 0.
- Verify Find: Check the same count after
DbSet.Find(); it should be 1.
Diagnostic Detail Needed: Are you using a shared DbContext instance across multiple requests (e.g., a Singleton), or is it scoped per request? Global no-tracking is typically used with scoped contexts to prevent memory leaks, but Singleton contexts require different disposal strategies.