Tracking is the mechanism by which Entity Framework performs tracking of changes in entities retrieved from the database.
Tracking is one of Entity Framework’s best features. But it can also be one of the most costly in terms of performance.
For this reason, sometimes it will be beneficial for us to deactivate the system using NoTracking, to optimize our applications.
This is especially important when working with large volumes of data or queries that do not require modifications to entities.
What is Tracking in Entity Framework?
Tracking is the system that allows Entity Framework to determine the changes when we make modifications to these entities.
With this, it can generate the necessary SQL statements to update the database when we call SaveChanges().
// Query with Tracking (default)
var product = context.Products.FirstOrDefault(p => p.Id == 1);
// We modify the entity
product.Price = 99.99m;
// We save the changes to the database
context.SaveChanges();
In this example,
- Entity Framework tracks the
productentity. - When we modify the price and call
SaveChanges(), - EF detects the change and generates an
UPDATESQL statement to update the record in the database.
But, logically, this change tracking comes with overhead, which we won’t always need. That’s why we can deactivate it if we don’t need it.
What is No-Tracking in Entity Framework?
No-Tracking is a technique that disables tracking of retrieved entities. This means Entity Framework will not track changes in these entities.
To disable tracking, we use the AsNoTracking() method.
using (var context = new ApplicationDbContext())
{
// Query with No-Tracking
var products = context.Products
.AsNoTracking()
.ToList();
// Changes to these entities will not be tracked
foreach (var product in products)
{
product.Price = 99.99m; // This change will not be reflected in the database
}
}
In this case,
- The retrieved entities are not being tracked by Entity Framework.
- Any modification we make to these entities will not be reflected in the database when calling
SaveChanges().
AsNoTracking with Projections
Sometimes, we don’t need to retrieve complete entities, but only certain fields. In these cases, we can also use AsNoTracking with projections to further improve performance.
using (var context = new ApplicationDbContext())
{
var products = context.Products
.AsNoTracking()
.Select(p => new { p.Name, p.Price })
.ToList();
}
In this example, we only retrieve the Name and Price fields of the products, which reduces the amount of data transferred and improves performance.
Updating an Entity Loaded with AsNoTracking
Even if we loaded an entity with AsNoTracking, we can still update it. But we will have to manage the state manually (we don’t have tracking).
To do this, we will have to attach it manually and change the state to Modified.
var entityWithoutTracking = context.Entities
.AsNoTracking()
.FirstOrDefault(e => e.Id == id);
// Make changes to the entity
entityWithoutTracking.Name = "New Name";
// Attach the entity to the context (still not tracking)
context.Attach(entityWithoutTracking);
// Attach and mark as modified
context.Entry(entityWithoutTracking).State = EntityState.Modified;
// Save the changes
context.SaveChanges();
You must ensure that the key fields are correctly assigned, as EF will use that to know which record to update (like the Id).
EF cannot detect which properties changed when you use State = Modified, so it will update all columns of that row, not just the modified ones.
If your entity has navigation properties, be careful with relationships you don’t want to touch; they could be updated or attempted to be inserted if not properly controlled.
There are more ways to change the state of a No-Tracking entity. For example, the Update command is basically equivalent to Attach and changing the state.
context.Update(product)
On the other hand, if we want to mark only the state of a property, we could do it like this:
context.Entry(product).Property(p => p.Price).IsModified = true;
When to Use No-Tracking
Entities loaded with No-Tracking reduce memory overhead and improve speed in database access (they improve it a lot!).
But of course, we lose tracking 🙄. So if we make changes to the entities, we won’t be able to save them (easily) with SaveChanges.
Therefore, it is useful for:
- Read-only queries: If you only need to retrieve data to display or process it without modifying it.
- Large volumes of data: When working with large datasets that require significant performance optimization.
