Change Tacking EF Core
One of the best features of EF is its ability to track changes done to the entities you work with. This way developers may focus their attention on business logic not managing entity states. Entity framework core keeps the state of the entity objects inside its DBContext.
How it all works
First you will need to pull an entity from the context. This is done by LINQ queries or methods like DbSet.ToList() or DbSet.FirstOrDefault() that is executed against the database you work with. All the entities returned from the query will be automatically tracked. The initial state of these entities is Unchanged.
var context = new SupermarketContext();
using (context)
{
var store = context.Stores.Where(s => s.Name == "Store 1").FirstOrDefault();
var storeEntry = context.Entry<Store>(store);
Console.WriteLine(storeEntry.State); //Unchanged
}
Some other scenario is when you build an web api and you receive a json object that represents your Entity object. Instead of pulling the object from the database first you may parse the json into an Entity object and attach it to the context. Here you will need the Id of the entity object so that the context knows exactly which the object is even if the object itself is not pulled from the database. When the object is attached the state of the object is again unchanged. When SaveChanges is called no actual command will be created for this object as the state is not marked as modified.
var context = new SupermarketContext();
using (context)
{
var store = new Store()
{
Id = 1,
Name = "Store 1",
Location = "Location 1",
};
context.Attach(store);
var storeEntry = context.Entry<Store>(store);
Console.WriteLine(storeEntry.State); // Unchanged
context.SaveChanges();
}
If you want to trigger updates on all fields you can mark the store entity as modified.
using (context)
{
var store = new Store()
{
Id = 1,
Name = "Store 1",
Location = "Location 1 - modified",
};
context.Attach(store);
var storeEntry = context.Entry<Store>(store);
Console.WriteLine(storeEntry.State); // Unchanged
storeEntry.State = EntityState.Modified;
context.SaveChanges();
}
Alternatively you can Update the store entity instead of explicitly changing the state. It will result in the same update.
var context = new SupermarketContext();
using (context)
{
var store = new Store()
{
Id = 1,
Name = "Store 1",
Location = "Location 1 - modified upadted state",
};
context.Update(store);
var storeEntry = context.Entry<Store>(store);
Console.WriteLine(storeEntry.State); // Modified
context.SaveChanges();
}
In case you only want to update one of the properties you can attach the store object and mark only the specific property as modified.
var context = new SupermarketContext();
using (context)
{
var store = new Store()
{
Id = 1,
Name = "Store 1",
Location = "Location 1 - only prop modification",
};
context.Attach(store);
var storeEntry = context.Entry<Store>(store);
Console.WriteLine(storeEntry.State); // Unchanged
storeEntry.Property(x=>x.Location).IsModified = true;
context.SaveChanges();
}
Note that if the attached entity doesn’t have an id its state is no longer Unchanged but Added.
var context = new SupermarketContext();
using (context)
{
var store = new Store()
{
Name = "New Store",
Location = "New Location",
OpenHours = 24
};
context.Attach(store);
var storeEntry = context.Entry<Store>(store);
Console.WriteLine(storeEntry.State); // Added
context.SaveChanges();
}
Linked items without id will be with state Added too and will be inserted into the database.
var context = new SupermarketContext();
using (context)
{
var inventory = new Inventory() { Name = "Soap" };
var store = new Store()
{
Id = 1,
Name = "New Store",
Location = "New Location",
OpenHours = 24,
Inventory = new List<Inventory>()
{
inventory
}
};
context.Attach(store);
var storeEntry = context.Entry<Store>(store);
Console.WriteLine(storeEntry.State); // Unchanged
var inventoryEntry = context.Entry<Inventory>(inventory);
Console.WriteLine(inventoryEntry.State); // Added
context.SaveChanges();
}
Linked items with id will be in Unchanged state.
Stop change tracking
Sometimes you may want to stop the tracking of an object and sometimes we may stop the tracking unintentionally. That is why it is a good idea to know when the change tracking stops.
Disposing the DBContext - One way to stop all tracking is by disposing of the DBContext.
Manually clearing the change tracker - Calling
dbContext.ChangeTracker.Clear()
will detach all entities and stop tracking them. This method is preferred when you want to detach all the entities versus detaching the entities one by one. Detaching all entities at once is much faster.You can always explicitly detach a single entity by changing its state
dbContext.Entry(entity).State = EntityState.Detached
.Use non-tracking queries - For read-only data, you can use
AsNoTracking
queries. This improves the performance of the queries.var inventories = context.Inventories.AsNoTracking().ToList();
State | Description | Action on Save Changes |
---|---|---|
Detached | Entity is not being tracked by the DbContext. | No action is taken. |
Added | Entity is new and will be inserted into the database. | INSERT operation is performed. |
Unchanged | Entity has not been modified since retrieval. | No action is taken. |
Modified | Entity has been updated since retrieval. | UPDATE operation is performed. |
Deleted | Entity is marked for deletion in the database. | DELETE operation is performed. |