DbContext Override SaveChanges to Audit Property Changes in Entity Framework 4.1

entity-framework

Question

I'm attempting to construct a limited "audit log" of property modifications for a collection of classes' properties. I've figured out how to set properties of the CreatedOn|ModifiedOn type, but I'm having trouble figuring out how to "discover" the property that has been changed.

Example:

public class TestContext : DbContext
{
    public override int SaveChanges()
    {
        var utcNowAuditDate = DateTime.UtcNow;
        var changeSet = ChangeTracker.Entries<IAuditable>();
        if (changeSet != null)
            foreach (DbEntityEntry<IAuditable> dbEntityEntry in changeSet)
            {

                switch (dbEntityEntry.State)
                {
                    case EntityState.Added:
                        dbEntityEntry.Entity.CreatedOn = utcNowAuditDate;
                        dbEntityEntry.Entity.ModifiedOn = utcNowAuditDate;
                        break;
                    case EntityState.Modified:
                        dbEntityEntry.Entity.ModifiedOn = utcNowAuditDate;
                        //some way to access the name and value of property that changed here
                        var changedThing = SomeMethodHere(dbEntityEntry);
                        Log.WriteAudit("Entry: {0} Origianl :{1} New: {2}", changedThing.Name,
                                        changedThing.OrigianlValue, changedThing.NewValue)
                        break;
                }
            }
        return base.SaveChanges();
    }
}

So, with EF 4.1 DbContext, is there a method to get the property that changed with this degree of specificity?

1
39
5/27/2011 7:27:14 PM

Accepted Answer

Very, very basic concept

foreach (var property in dbEntityEntry.Entity.GetType().GetProperties())
{
    DbPropertyEntry propertyEntry = dbEntityEntry.Property(property.Name);
    if (propertyEntry.IsModified)
    {
        Log.WriteAudit("Entry: {0} Original :{1} New: {2}", property.Name,
            propertyEntry.OriginalValue, propertyEntry.CurrentValue);
    }
}

Although I have no idea how it might really function in practice, I would at least give it a go. Naturally, there may be numerous properties that have changed, which is why the loop and potentially multiple calls ofWriteAudit .

However, the reflection-related code in SaveChanges may cause performance issues.

Edit

It could be preferable to have access to the underlyingObjectContext . If so, then the following is feasible:

public class TestContext : DbContext
{
    public override int SaveChanges()
    {
        ChangeTracker.DetectChanges(); // Important!

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

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

       foreach (ObjectStateEntry entry in objectStateEntryList)
       {
           if (!entry.IsRelationship)
           {
               switch (entry.State)
               {
                   case EntityState.Added:
                       // write log...
                       break;
                   case EntityState.Deleted:
                       // write log...
                       break;
                   case 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
                           {
                               Log.WriteAudit(
                                   "Entry: {0} Original :{1} New: {2}",
                                   entry.Entity.GetType().Name,
                                   oldValue, newValue);
                           }
                       }
                       break;
                   }
               }
           }
       }
       return base.SaveChanges();
    }
}

I personally used this in EF 4.0. I am unable to discover a technique that corresponds toGetModifiedProperties (which is the essential component to evade the reflection code) in theDbContext API.

Edit 2

When interacting with POCO instances, the code above must callDbContext.ChangeTracker.DetectChanges() at the start. Because of thisbase.SaveChanges is termed here too late (at the end of the method).base.SaveChanges calls DetectChanges internally, but we must first call since we want to examine and record the changes.DetectChanges manually in order for EF to locate all updated properties and properly set the states in the change tracker.

There may be circumstances in which the code may function without callingDetectChanges , for instance, if the DbContext/DbSet proceduresAdd or Remove are employed after the most recent property adjustments since these techniques also demand forDetectChanges internally. However, if, for example, an object is simply imported from a database and a few properties are updated, this derivedSaveChanges is referred to, automated change detection has not occurred in the pastbase.SaveChanges , ultimately leading to a lack of log entries for changed properties.

Accordingly, I altered the aforementioned code.

44
9/7/2011 2:23:47 PM

Popular Answer

You may use the techniques Slauma recommends, however rather than overriding theSaveChanges() With a technique, you may manageSavingChanges even with a far simpler implementation.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow