Search code examples
c#asp.net-mvcentity-frameworkentity-framework-6

Override SaveChangesAsync Entity Framework 6 to log modified property values


I'm developing an ASP.NET Core project where in SaveChanges, I need to log the updated values

According to this I tried to override SaveChangesAsync but I get this error :

Unable to cast object of type 'MyProject.ApplicationDbContext' to type 'System.Data.Entity.Infrastructure.IObjectContextAdapter'.

Code:

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    ChangeTracker.DetectChanges();

    ObjectContext ctx = ((IObjectContextAdapter)this).ObjectContext;

    List<ObjectStateEntry> objectStateEntryList = ctx.ObjectStateManager
                                                     .GetObjectStateEntries((System.Data.Entity.EntityState)(EntityState.Added | EntityState.Modified | EntityState.Deleted))
                                                     .ToList();

    foreach (ObjectStateEntry entry in objectStateEntryList)
    {
        if (!entry.IsRelationship)
        {
            switch (entry.State)
            {
                case (System.Data.Entity.EntityState)EntityState.Added:
                    // write log...
                    break;

                case (System.Data.Entity.EntityState)EntityState.Deleted:
                    // write log...
                    break;

                case (System.Data.Entity.EntityState)EntityState.Modified:
                    foreach (string propertyName in entry.GetModifiedProperties())
                    {
                        DbDataRecord original = entry.OriginalValues;
                        string oldValue = original.GetValue(original.GetOrdinal(propertyName)).ToString();

                        CurrentValueRecord current = entry.CurrentValues;
                        string newValue = current.GetValue(current.GetOrdinal(propertyName)).ToString();

                        if (oldValue != newValue) // probably not necessary
                        {
                            var a = string.Format("Entry: {0} Original :{1} New: {2}",
                                                  entry.Entity.GetType().Name,
                                                  oldValue, newValue);
                        }
                    }
                    break;
           }
       }
   }

   return base.SaveChangesAsync();
}

Also I tried to compose the log string in the Action method but I get the same error.

public async Task<IActionResult> Edit(MyModel model)
{
    ...
    // _context is ApplicationDbContext()
    var myObjectState = _context.ObjectStateManager.GetObjectStateEntry(model);
    var modifiedProperties = myObjectState.GetModifiedProperties();

    foreach (var propName in modifiedProperties)
    {
        Console.WriteLine("Property {0} changed from {1} to {2}",
                          propName,
                          myObjectState.OriginalValues[propName],
                          myObjectState.CurrentValues[propName]);
    }
}

I'm using EF 6.

I need to log the property name, old value and new value. I can't get why I'm getting that error, what am I doing wrong?


Solution

  • what am I doing wrong?

    Using an example from EF 4.1? :)

    You can utilize the ChangeTracker in EF 6 to accomplish what you are looking for. I would recommend setting up a separate DbContext for your logging containing just the logging table and FKs as needed:

    Within your SaveChanges/SaveChangesAsync overrides:

    var updatedEntities = ChangeTracker.Entries()
        .Where(x => x.State == EntityState.Modified);
    
    var insertedEntities = ChangeTracker.Entries()
        .Where(x => x.State == EntityState.Added);
    

    From here you can access the original and modified property values through OriginalValues and CurrentValues respectively.

    ** Update ** When reporting on changes the typical easy way is to use ToObject() on the OriginalValues and CurrentValues then serialize to something like Json to capture the before and after state of the object in question. However this will display all modified and unmodified values.

    To iterate through the values to find actual changes is a bit more involved:

    foreach(var entity in updatedEntities)
    {
        foreach (var propertyName in entity.OriginalValues.PropertyNames)
        {
            if (!object.Equals(entity.OriginalValues.GetValue<object>(propertyName), 
            entity.CurrentValues.GetValue<object>(propertyName)))
            {
                var columnName = propertyName;
                var originalValue = entity.OriginalValues.GetValue<object>(propertyName)?.ToString() ?? "#null";
                var updatedValue = entity.CurrentValues.GetValue<object>(propertyName)?.ToString() ?? "#null";
                var message = $"The prop: {columnName} has been changed from {originalValue} to {updatedValue}";
            }
        }
    }