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.Entry
method
For Example, the following code access the EntityEntry
of the department
entity and sets its Entity State as Deleted
1 2 3 | 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
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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.
1 2 3 | db.Entry(department1).State = EntityState.Detached; |