While studying EF Core 7, I tried this code:
using CompanyDBContext dbContext = new CompanyDBContext();
var emp = dbContext.Employees.First(e => e.Name == "Yousef");
emp.Name = "Yousef2";
emp.Name = "Yousef";
Console.WriteLine(dbContext.Entry(emp).State); // Output is unchanged
I know that EF Core takes a snapshot when it retrieves an object from the database to track changes.
In the previous example, it determined that the name property had not changed in the end.
But in this example:
using CompanyDBContext dbContext = new CompanyDBContext();
var emp = dbContext.Employees.First(e => e.Name == "Yousef");
emp.Name = "Yousef2";
Console.WriteLine(dbContext.Entry(emp).State); // Output: Modified
emp.Name = "Yousef"; // Like the original value.
Console.WriteLine(dbContext.Entry(emp).State); // Output: Modified !!!!!
I do not know why it became modified, even though I returned its value to the original. Why didn't EF compare the new value with the snapshot value and keep the state unchanged?
EF does an internal call to DetectChanges
on many occasions, one of which is calling DbContext.Entry
. Since change tracking is an expensive operation (esp. when there are many items in the context's cache), EF tries to make this call as economical as possible. Therefore, once an entry is Modified
it isn't checked again.
This can be verified by a litte bit of code:
Taking ths simple class, of which EF stored some instances in the database:
class Test
{
public int Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return $"Test, Name = '{Name}'";
}
}
And a context that listens to cthe hange tracker's StateChanged
event:
this.ChangeTracker.StateChanged += ChangeTracker_OnStateChanged;
Running this code (in Linqpad):
var db = getContext();
var test = db.Tests.Find(1);
var org = test.Name;
test.Name = "a";
db.Entry(test).State.Dump();
test.Name = org;
db.Entry(test).State.Dump();
This is the result:
Test, Name = 'a' state changed Unchanged => Modified
Modified
Modified
When the first db.Entry(test).State.Dump();
is commented out the output is simply
Unchanged
Of course it's not a common use case to reset a property's value to its original value, but if you want to reset an entity object's state you could do this:
db.Entry(test).CurrentValues.SetValues(db.Entry(test).OriginalValues);
db.Entry(test).State = EntityState.Unchanged;