Tutorials Menu

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();
 }
Modified state

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();
}
Modified state

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();
 }
Modify only the property

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();
 }
Added state based on no id

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();
 }
Added state based on no id

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.

  1. Disposing the DBContext - One way to stop all tracking is by disposing of the DBContext.

  2. 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.

  3. You can always explicitly detach a single entity by changing its state dbContext.Entry(entity).State = EntityState.Detached.

  4. 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.