The ChangeTracker in EF Core tracks changes made to every entity by assigning them the Entity States. It uses the EntityEntry class to store the tracking information of a given entity. The Entity States represents the state of an entity. The Entity State can be Added, Deleted, Modified, Unchanged or Detached. For example, when we add a new entity to DbContext using the Add / AddRange method, the DbContext sets the state of the entity as Added. When we query and load the entity the state is set to Unchanged. If we make any changes to the entity then its state becomes Modified The SaveChanges uses these states to determine to generate the SQL query ( Insert,Update or Delete )
Source Code:
The source code of this project available in GitHub. It also contains the script of the database
Table of Contents
ChangeTracker
The ChangeTracker class is responsible for keeping track of entities loaded into the Context. It does so by creating an EntityEntry class instance for every entity. The ChnageTracker maintains the Entity State, the Original Values, Current Values, etc of each entity in the EntityEntry class.
EntityEntry
Each entity tracked by the context gets an instance of the EntityEntry class. It stores the Change tracking information of the given entity and also has methods, which you can use to manipulate the Change Tracking Information.
You can access instance of the EntityEntry of a given entity, using the DbContext.Entrymethod
For Example, the following code access the EntityEntry of the department entity and sets its Entity State as Deleted
db.Entry(department).State = EntityState.Deleted;Some of the important Property & methods of the EntityEntry
| Property/Method | Purpose |
|---|---|
| Collections | Provides access to change tracking information and loading information for all collection navigation properties of this entity. |
| Context | Gets the context that is tracking the entity. |
| CurrentValues | Gets the context that is tracking the entity. |
| Entity | Gets the context that is tracking the entity.. |
| OriginalValues | Gets the original property values for this entity. The original values are the property values as they were when the entity was retrieved from the database. |
| State | Gets or sets that state that this entity is being tracked in. |
| References | Provides access to change tracking information and loading information for all reference (i.e. non-collection) navigation properties of this entity. |
| DetectChanges() | Scans this entity instance to detect any changes made to the instance data. DetectChanges() is usually called automatically by the context to get up-to-date information on an individual entity before returning change tracking information. You typically only need to call this method if you have disabled AutoDetectChangesEnabled. |
| Collection(String) | Provides access to change tracking and loading information for a collection navigation property that associates this entity to a collection of another entities. |
| Navigation(String) | Provides access to change tracking information and operations for a given navigation property of this entity. |
| Property(String) | Provides access to change tracking information and operations for a given property of this entity. |
| Reference(String) | Provides access to change tracking and loading information for a reference (i.e. non-collection) navigation property that associates this entity to another entity. |
| Reload() | Reloads the entity from the database overwriting any property values with values from the database. The entity will be in the Unchanged state after calling this method, |
Entity States
As we mentioned earlier, the ChangeTracker tracks the Entity states of an entity using one of the following states.
- Added
- Deleted
- Modified
- Unchanged
- Detached
Added
When we add an entity to the context, then the ChangeTracker sets the status as Added. It indicates that the entity exists in the context, but does not exist in the database. The DbContext generates the INSERT SQL query and insert the data into the database when the user invokes the SaveChanges method. Once the SaveChanges is successful, the state of the entity changes to Unchanged
In the following example shows the Entity State before and after SaveChanges.
public void AddStatusExample()
{
using (EFCoreContext db = new EFCoreContext())
{
Department department = new Department();
department.Name = "Production";
db.Add(department);
Console.WriteLine("Status Before SaveChanges " + db.Entry(department).State.ToString()); //Added
db.SaveChanges();
Console.WriteLine("Status After SaveChanges " + db.Entry(department).State.ToString()); //Unchanged
}
Console.WriteLine("Press any key to continue ");
Console.ReadKey();
}Unchanged
The property values of the entity have not been modified since context retrieved it from the database. The SaveChanges ignores this entity. The tracker also sets the State as Unchanged after a successful SaveChange as you can see it from the previous example.
Modified
The entity state becomes Modified, when the user makes changes to the entity. It also indicates that the entity exists in the database. The DbContext generates the update SQL Query to update the entity in the database. Once the SaveChanges is successful, the state of the entity changes to Unchanged
public void ModifiedStatusExample()
{
using (EFCoreContext db = new EFCoreContext())
{
Department department = db.Departments.Where(f => f.Name == "Production").FirstOrDefault();
department.Name = "Production Department";
Console.WriteLine("Status Before SaveChanges " + db.Entry(department).State.ToString()); //Modified
db.SaveChanges();
Console.WriteLine("Status After SaveChanges " + db.Entry(department).State.ToString()); //Unchanged
}
Console.WriteLine("Press any key to continue ");
Console.ReadKey();
}In Connected Scenario, the Entity framework Core also keeps track of the changes made to the properties of the entity. The context updates only those columns whose values are modified.
Deleted
The Deleted entity state indicates that the entity is marked for deletion, but not yet deleted from the database. It also indicates that the entity exists in the database. The DbContext generates the delete SQL Query to remove the entity from the database. The DbContext removes the entity from the context once the delete operation succeeds after the SaveChanges. I.e if you check the status after SaveChanges you will find it as Detached.
public void DeletedStatusExample()
{
using (EFCoreContext db = new EFCoreContext())
{
Department department = db.Departments.Where(f => f.Name == "HR").FirstOrDefault();
db.Remove(department);
Console.WriteLine("Status After SaveChanges " + db.Entry(department).State.ToString()); //Deleted
db.SaveChanges();
Console.WriteLine("Status After SaveChanges " + db.Entry(department).State.ToString()); //Detached
}
Console.WriteLine("Press any key to continue ");
Console.ReadKey();
}Detached
The Detached entity state indicates that the DbContext is not tracking the entity.
Attach
Entity Framework Core Attach method allows us to attach an Detached entity to context and start tracking it. In the following code, we are attaching two entities (Department1 & Department2). Note that in Department2 we have set the value of DepartmentID, which is a Primary Key. Notice that after the attach, the entity with a primary key is set as Unchanged, while the entity without a primary key is set as Added.
public void AttachExample()
{
Console.WriteLine("Attach Example");
Department department1 = new Department();
department1.Name = "Production";
Department department2 = new Department();
department2.DepartmentID = 10;
department2.Name = "Finance";
using (EFCoreContext db = new EFCoreContext())
{
Console.WriteLine("Status Before Attach department1 " + db.Entry(department1).State.ToString()); //Detached
Console.WriteLine("Status Before Attach department2 " + db.Entry(department2).State.ToString()); //Detached
db.Attach(department1);
db.Attach(department2);
Console.WriteLine("Status After Attach department1 " + db.Entry(department1).State.ToString()); //Added
Console.WriteLine("Status After Attach department2 " + db.Entry(department1).State.ToString()); //Unchanged
}
Console.WriteLine("Press any key to continue ");
Console.ReadKey();
}You can also attach an detached entity to the context just by setting entity state to anything other than Detached
Detach
You can set the entity state as Detached to detach an entity from the context.
db.Entry(department1).State = EntityState.Detached;