[C#] Entity Framework Core -ChangeTracker-

changeTracker

Hello, I'm Eita

In the past, I've summarized how relationships are established in EF Core

Having worked with Laravel (Eloquent), I found EF Core's mechanism of "inferring relationships even without explicit specification" a little strange

In the previous article, I used that feeling of unease as a starting point to organize how EF Core interprets the relationships between entities and treats them as an internal model

[C#] Entity Framework Core -Relationship Establishment Conditions-

[C#] Entity Framework Core -Relationship Establishment Conditions-

With that in mind, let's clarify how EF Core manages entity changes, specifically through its ChangeTracker

In the course of my work, I encountered a situation where an unexpected commit was about to occur,

  • What changes does EF Core remember?
  • What is the management unit for DbContext and ChangeTracker ?
  • Timing to be aware of during implementation

I'm leaving this here as a personal memo

I hope this helps someone with their organization

What is ChangeTracker?

First,ChangeTracker isDbContext is currently tracking.

In EF Core, the DbContext remembers entities retrieved from the database, as well as entities that have been added , updated , or removed

Then, when SaveChanges() is called, INSERT / UPDATE / DELETE operations are executed based on the state managed by ChangeTracker

To put it simply, the relationship is as follows:

  • DbContext: Unit of work with the database
  • ChangeTracker: A tool for tracking what has changed within a given work unit.
  • SaveChanges(): Reflects remembered changes in the database.

In other words, EF Core doesn't manually write SQL every time;instead, it determines the necessary SQL based on the current state of the DbContext.

Entity state

ChangeTracker manages entities in several states.

Typical conditions are as follows:

situation meaning What happens when SaveChanges is executed?
Detached Not tracked by DbContext Do nothing
Unchanged After obtaining the database, no changes have been made Do nothing
Added Newly added INSERT
Modified Changed English:
Deleted Scheduled for deletion DELETE

For example, if you change the value of an entity retrieved from the database and call SaveChanges() , EF Core will detect the change and perform an UPDATE

// Retrieve from DB: Unchanged var user = context.Users.First(); // Update property: Modified user.Name = "After Change"; // Changes are detected and UPDATE is executed when SaveChanges() is called context.SaveChanges();

The important point here is that the changes are not yet reflected in the database until SaveChanges() is called

ChangeTracker simply remembers "planned changes" within the DbContext .

When does the ChangeTracker state change?

Next, let's organize the moments when the entity's state changes

timing example situation
When retrieved from the DB using a regular query Context.Users.FirstAsync() Unchanged
When you add a new one context.Users.Add(user) Added
When you tell someone that an object created with ` new` "already exists in the database" Context.Users.Attach(user) Unchanged
When you change the properties of a tracked entity user.Name = "After Change" Modified
When you want to force a detached object to be updated Context.Users.Update(user) Modified
When you select to delete Context.Users.Remove(user) Deleted

Generally, regular queries in EF Core are tracked .

Therefore, the entities retrieved as follows are tracked in the DbContext

var users = context.Users.ToList();

On the other hand, tracking does not occur when using AsNoTracking()

var users = context.Users .AsNoTracking() .ToList();

In this case, even if you change the retrieved users , ChangeTracker will not remember that change.

Therefore, if the data is only referenced and not updated , you can avoid unnecessary tracking by using AsNoTracking()

When will I be removed from ChangeTracker?

Entities tracked by ChangeTracker do not remain indefinitely

Tracking will primarily cease at the following times:

timing Content
When DbContext is Dispose Normal tracking end timing
ChangeTracker.Clear() is called Remove all entities currently tracked by the DbContext
When Entry(entity).State = EntityState.Detached Exclude specific entities from tracking
When deletion is successful with SaveChanges() Deleted entities become Detached.

State after SaveChanges()

of SaveChanges() will basically be as follows:

SaveChanges After SaveChanges
Added Unchanged
Modified Unchanged
Deleted Detached

In other words, entities that have been added or updated will continue to be tracked as "Unchanged" even after the changes are reflected in the database

On the other hand, deleted entities are treated as if they have disappeared from the database and are therefore classified as Detached

List of state transitions

Based on what we've covered so far, let's summarize the basic state transitions

operation state transition Impact / Supplement
new Entity Detached Immediately after creating a new instance, the DbContext is not yet aware of its existence.
Add(entity) Detached → Added Inserted in SaveChanges ( )
Get data from the database? Detached → Unchanged The state immediately after data acquisition, with no changes
Change the value of the acquired data Unchanged → Modified Since the value has changed, it will be automatically updated.
Remove(entity) Unchanged → Deleted I plan to delete data from the database.
SaveChanges() success (add/update) Added / Modified → Unchanged Saving is complete, and there are no changes compared to the database
SaveChanges() successful (deletion) Deleted → Detached It disappears from the database and is removed from the managed list
Update(entity) Detached → Modified The data created using `new` is used as the target for `UPDATE` without ever using `SELECT`.

Note: A state that DbContext is unaware of is called Detached . For example, an object that you have simply created using `new` , or an entity obtained with `AsNoTracking()` , is not tracked and is in a state that DbContext is unaware of.

Things you should know when using ChangeTracker

Do not include in tracking (As No Tracking)

As mentioned earlier, if the data is only referenced and not updated, you can exclude it from ChangeTracker tracking by using AsNoTracking()

var users = context.Users .AsNoTracking() .ToList();

Read-only processing avoids unnecessary tracking, thus preventing wasted memory consumption and unintended updates

Managed on a DbContext basis

The ChangeTracker is not shared across the entire application; it exists for each DbContext instance

Therefore, even though they are the same entity, the following are different things

  • State being tracked in A's DbContext
  • The state being tracked in B's DbContext

If you are not aware of this unit,

  • How many changes are remembered?
  • Why was this change also reflected in SaveChanges() ?

This will make it difficult to understand

Note: DbContext lifetime

a DbContext is determined by the settings configured during DI registration.

When you register a DbContext using AddDbContext in an ASP.NET Core web application , the DbContext is registered as Scoped by default

Therefore, the same DbContext instance is usually used within a single request.

The official documentation states that AddDbContext is registered as Scoped by default, and that in many ASP.NET Core applications, each request will have a different scope, i.e., a different DbContext

DbContext lifetime

To check how DbContext is registered in your own code , refer to the registration section in the following files, etc

  • Startup.cs
  • Program.cs

Timing to be careful about during implementation

ChangeTracker is a convenient tool, but if you're not aware of what you're tracking, it can lead to unexpected updates.

Personally, I think the following are times when you should be especially careful

timing Problems that occur
Rollback / When an exception occurs Even if the transaction on the database side is reversed, the ChangeTracker does not automatically revert to its state before the rollback, which can lead to a discrepancy between the state in the database and the state remembered by DbContext.
Long-running batch processing, etc Previously handled data continues to accumulate in the tracking list, potentially leading to wasted memory consumption and unintended involvement in updates
Read-only queries If tracking is enabled even though it's only for displaying information on the screen, it can lead to unnecessary memory consumption and potentially cause accidental updates

Furthermore, I believe the following points should be kept in mind regarding these matters

  • After use: Dispose of items instead of reusing them unnecessarily. Be mindful of their lifespan.
  • If you can't separate them, Clear(): Manually reset the tracking state immediately after a rollback, for example, when handling errors within the same Context.
  • if you don't intend to update Use AsNoTracking(): Add this to places that don't save data, such as list displays, to avoid unnecessary tracking.

What exactly makes ChangeTracker so useful?

Up to this point, we have focused on and organized information regarding the state management and points to note about ChangeTracker

Therefore, let me also summarize the advantages of ChangeTracker

1. The same entity can be treated as the same instance

While managed by ChangeTracker within the same DbContext , if multiple entities with the same primary key exist within the DbContext , it becomes ambiguous as to which instance's state should be saved as "correct"

To avoid this situation, EF Core treats entities with the same primary key within the same DbContext as a single instance (Identity Resolution).

Identity Resolution is a mechanism necessary for ChangeTracker to manage the entity state consistently,

I found it convenient that even when handling the same data multiple times within the same DbContext , it can be treated as the same instance, making it easier to manage the same data in a consistent state. (Of course, you still need to be aware of which entity is being tracked.)

2. It automatically determines the necessary SQL based on the entity's state and relationships

As explained above, EF Core generates and executes the necessary SQL statements for INSERT , UPDATE , and DELETE operations without requiring you to manually construct them

Furthermore, if the relationships between entities are correctly established, EF Core will handle the necessary SQL execution order and foreign key binding, including for child entities linked to parent entities

var user = new User { Name = "Yamada Taro", Orders = new List<Order> { new Order { ProductName = "Product A" }, new Order { ProductName = "Product B" } } }; context.Users.Add(user); context.SaveChanges();

In this example, if the relationship between User and Order is correctly built in the IModel , EF Core can understand the relationship between user and order data

Therefore, after registering a user, the system links the assigned user ID as an external key for the order data and then registers the order data

In other words,ChangeTracker manages the state of not only individual entities but also related entities.

Then, SaveChanges() , EF Core uses that state information and model information to determine and execute the necessary SQL and its execution order.

Let's briefly summarize the ChangeTracker workflow

Finally, let's summarize the process of how ChangeTracker remembers changes and reflects them in the database

Change management workflow using WorkTracker

Retrieve entities via DbContext ↓ ChangeTracker tracks the entities (default is Unchanged) ↓ Perform property changes / Add / Remove etc. ↓ The state on ChangeTracker changes (Modified / Added / Deleted) ↓ Call SaveChanges() ↓ EF Core executes UPDATE / INSERT / DELETE based on the state held by ChangeTracker ↓ The state is updated to the saved state (Modified / Added becomes Unchanged, Deleted becomes Detached)

summary

So far, we have summarized the role of ChangeTracker in EF Core and how it manages the state of entities

ChangeTracker is a mechanism for managing the state of entities and reflecting necessary changes to the database when SaveChanges() is called

It automatically detects changes to retrieved entities and generates the necessary SQL queries, so you can reflect object changes in the database without having to explicitly write detailed update code

On the other hand, if you are not aware of what is being tracked, unexpected changes may be reflected in the database at the time of SaveChanges()

Therefore, when dealing with EF Core,

  • What's in the database?
  • the current DbContext remember?

I realized that it's important to be aware of both perspectives

That's it!!

If you found this article helpful,please give it a "Like"!
1
1
7
X Facebook Hatena Bookmark pocket

The person who wrote this article

About the author

Eita

Web development engineer,
just want to get out there.