entity-frameworkentity-framework-5entity-framework-6entity-framework-extended

Entity Framework 6 Batch Updates and AuditLog using Entity Framework Extended


I am using Entity Framework 6 and I am using EntityFramework Extended to perform some batch updates and batch deletes. The batch updates and batch deletes work OK however I also need to know the entities that were updated / deleted (i.e. the current and previous values). I thought that using the AuditLogger provided by EntityFramework.Extended would provide me with the details of the entities that where updated or deleted however this does not seem to be the case. For example, using the code below i.e.

var auditor =dbContext.BeginAudit();
dbContext.Addresses.Update(ent => new Address { AddressId = 1190 });
dbContext.SaveChanges();
var changes = auditor.LastLog;

This is simple batch update to update all addressIds to 1190. If I inspect changes.Entities, it returns a Count of 0 i.e. an empty list.

What I was expecting was that changes.Entities would contain all the 'old' entities with the old values before the addressId was changed to 1190.

Am I mistaken or is this indeed the correct behaviour? How can I get an audit log of all updated entities when using Entity framework Extended batch update / delete

Thanks


Solution

  • You should enable the Auditor

    var auditConfiguration = AuditConfiguration.Default;
    auditConfiguration.IncludeRelationships = true;
    auditConfiguration.LoadRelationships = true;
    auditConfiguration.DefaultAuditable = true;
    

    just add it to place where your app is initialized, Global.asax.cs for example

    edited

    Far as I know you can't get old values using EF.Extended. Here is my solution:

    override SaveChanges method in the Context

    public override int SaveChanges()
        {
            return SaveChanges(false);
        }
    
        public int SaveChanges(bool disableAudit)
        {
            var result = -1;
            try
            {
                if (!disableAudit)
                {
                    foreach (var entity in ChangeTracker.Entries().Where(x => x.State == EntityState.Added ||
                                                                              x.State == EntityState.Modified ||
                                                                              x.State == EntityState.Deleted))
                    {
                        ProccessAuditLog(entity);
                    }
                }
            }
            catch (Exception ex)
            {
                // handle the ex here
            }
            finally
            {
                //save changes
                result = base.SaveChanges();
            }
    
            return result;
        }
    

    and add method to process audit logging:

    private void ProccessAuditLog(DbEntityEntry entry)
        {
            var entity = entry.Entity;
            var entityType = GetEntityType(entity.GetType());
    
            var oldValue = Activator.CreateInstance(entityType); ;
            if (entry.State == EntityState.Modified)
            {
                // entry.OriginalValues doesn't load navigation properties for changed entity so we should reload the object from db to get it
                // save current values
                var newValue = Activator.CreateInstance(entityType);
                Mapper.DynamicMap(entity, newValue, entity.GetType(), entityType);
    
                // reload old values for entity from the db
                entry.Reload();
                Mapper.DynamicMap(entry.Entity, oldValue, entity.GetType(), entityType);
    
                // revert reloading changes in entity
                entry.CurrentValues.SetValues(newValue);
                entry.OriginalValues.SetValues(oldValue);
                entry.State = EntityState.Modified;
                entity = newValue;
            }
    
            if (entry.State == EntityState.Deleted)
            {
                // reload old values for entity from the db
                entry.Reload();
                Mapper.DynamicMap(entry.Entity, oldValue, entity.GetType(), entityType);
    
                // revert reloading changes in entity
                entry.OriginalValues.SetValues(oldValue);
                entry.State = EntityState.Deleted;
                entity = null;
            }
    
            // here is you can proccess old entity in 'oldValue' and new entity in 'entity'
            // then save your log to db using SaveChanges(true) to prevent StackOverFlow exception
        }
    

    As Mapper you can use AutoMapper

    method to get base entity's type instead proxy type:

    private Type GetEntityType(Type entityType)
            {
                return entityType.BaseType != null && entityType.Namespace == "System.Data.Entity.DynamicProxies"
                        ? entityType.BaseType
                        : entityType;
            }
    

    Hope it will help you