Entity Framework DbContext SaveChanges() OriginalValue Incorrect

22,352

Solution 1

When EF retrieves an entity from the database it takes a snapshot of the original values for all properties of that entity. Later, as changes are made to the values of these properties the original values will remain the same while the current values change.

However, for this to happen EF needs to be tracking the entity throughout the process. In a web or other n-tier application, typically the values are sent to the client and the context used to query the entity is disposed. This means that the entity is now no longer being tracked by EF. This is fine and good practice.

Once the application posts back the entity is reconstructed using values from the client and then re-attached to the context and set into a Modified state. However, by default the only values that come back from the client are the current values. The original values are lost. Usually this doesn't matter unless you are doing optimistic concurrency or want to be very careful about only updating values that have really changed. In these cases the original values should also be sent to the client (usually as hidden fields in a web app) and then re-applied as the original values as a part of the attach process. This was not happening in the example above and this is why the original values were not showing as expected.

Solution 2

If you change

dbEntry.OriginalValues.GetValue<object>(propertyName);

to

dbEntry.GetDatabaseValues().GetValue<object>(propertyName);

then that works.

Share:
22,352
Joe DePung
Author by

Joe DePung

Updated on July 09, 2022

Comments

  • Joe DePung
    Joe DePung almost 2 years

    I am trying to implement an AuditLog using EF 4.1, by overriding the SaveChanges() method as discussed in the following places:

    I am having problems with the "modified" entries though. Whenever I attempt to get at the OriginalValue of the property in question, it always has the same value as it does in the CurrentValue field.

    I first use this code, and it successfully identifies the Entries that are modified:

    public int SaveChanges(string userID)
    {
    
        // Have tried both with and without the following line, and received same results:
        // ChangeTracker.DetectChanges();
    
        foreach (
          var ent in this.ChangeTracker
                         .Entries()
                         .Where( p => p.State == System.Data.EntityState.Added ||
                                         p.State == System.Data.EntityState.Deleted ||
                                         p.State == System.Data.EntityState.Modified ))
        {
            // For each change record, get the audit record entries and add them
            foreach (AuditLog log in GetAuditRecordsForChange(ent, userID))
            {
                this.AuditLog.Add(log);
            }
    
        }
    
        return base.SaveChanges();
    }
    

    The problem is in this (abbreviated code):

        private List<AuditLog> GetAuditRecordsForChange(DbEntityEntry dbEntry, string userID)
        {
            if (dbEntry.State == System.Data.EntityState.Modified)
            {
                foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
                {
                    if (!object.Equals(dbEntry.OriginalValues.GetValue<object>(propertyName),
                        dbEntry.CurrentValues.GetValue<object>(propertyName)))
                    {
                            // It never makes it into this if block, even when
                            //    the property has been updated.
                    }
    
                    // If I updated the property "Name" which was originally "OldName" to the value "NewName" and then break here and inspect the values by calling:
                    //      ?dbEntry.OriginalValues.GetValue<object>("Name").ToString()
    
                    // the result will be "NewName" and not "OldName" as expected
                 }
             }
        }
    

    The strange thing is that the call to dbEntry.Property(propertyName).IsModified(); will return true in this case. It is just that the OriginalValue doesn't have the expected value inside. Would anyone be willing to help point me in the right direction? I cannot seem to get this to work correctly.

  • Joe DePung
    Joe DePung about 12 years
    Thanks again for your help. I've got it working as expected now... I think. Just for clarification: Are you saying that calling context.Entry(product).State = EntityState.Modified re-attaches 'product' to the context and therefore it loses track of its OriginalValues? Also, here is the new post I made for my follow up (just for reference for others).
  • eoleary
    eoleary about 11 years
    Works, but presumably very expensive for auditing purposes.
  • sintetico82
    sintetico82 over 8 years
    Why very expansive? If you want to know the value before and after the change you have to query the database, no way.
  • Jensen
    Jensen over 8 years
    But why is the approach working in the Change Tracking Example linked in @JoeDePung question?
  • Abi
    Abi almost 7 years
    it works.. but what is the reason the first one does not hold the original value and why the same as currentvalue @eoleary
  • user2555515
    user2555515 over 4 years
    He is overwriting SaveChanges() and I assume he calls it before returning the data to the client and before disposing of the context.