Search code examples
c#entity-framework-coreorm

Unexpected behaviour in Entity Framework Core 7


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?


Solution

  • 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;