How can I log all entities change, during .SaveChanges() using EF code first?

Masoud picture Masoud · Jul 28, 2013 · Viewed 29.7k times · Source

I'm using EF code first. I'm using a base Repository for all my repositories and an IUnitofWork that inject to the repositories, too:

public interface IUnitOfWork : IDisposable
{
    IDbSet<TEntity> Set<TEntity>() where TEntity : class;
    int SaveChanges();
}

public class BaseRepository<T> where T : class
{
    protected readonly DbContext _dbContext;
    protected readonly IDbSet<T> _dbSet;


    public BaseRepository(IUnitOfWork uow)
    {
        _dbContext = (DbContext)uow;
        _dbSet = uow.Set<T>();
    }
    //other methods
}   

e.g my OrderRepository is like this:

class OrderRepository: BaseRepository<Order>
{
    IUnitOfWork _uow;
    IDbSet<Order> _order;

    public OrderRepository(IUnitOfWork uow)
        : base(uow)
    {
        _uow = uow;
        _order = _uow.Set<Order>();
    }
    //other methods
}

And I use it in this way:

public void Save(Order order)
{
        using (IUnitOfWork uow = new MyDBContext())
        {
            OrderRepository repository = new OrderRepository(uow); 
            try
            {
               repository.ApplyChanges<Order>(order);    
               uow.SaveChanges();
            }  

        } 
}     

Is there any way to log change histories of all entities(include their navigation properties) during .SaveChanges()? I want to log original values(before save occurs) and changed values(after save occurs).

Answer

Steve Ruble picture Steve Ruble · Aug 4, 2013

You can get the before and after values for all changed entities by going through DbContext.ChangeTracker. Unfortunately the API is a little verbose:

var changeInfo = context.ChangeTracker.Entries()
            .Where (t => t.State == EntityState.Modified)
            .Select (t => new {
                Original = t.OriginalValues.PropertyNames.ToDictionary (pn => pn, pn => t.OriginalValues[pn]),
                Current = t.CurrentValues.PropertyNames.ToDictionary (pn => pn, pn => t.CurrentValues[pn]),
            });

You can modify that to include things like the type of the entity if you need that for your logging. There is also a ToObject() method on the DbPropertyValues (the type of OriginalValues and CurrentValues) you could call if you already have a way to log whole objects, although the objects returned from that method will not have their navigation properties populated.

You can also modify that code to get all entities in the context by taking out the Where clause, if that makes more sense given your requirements.