Intercept / Decorate Entity Framework's DbContext.SaveChanges() through Simple Injector

3.9k Views Asked by At

I am writing an AuditLog based on the changes tracked in the Context object in Entity Framework.

I need to be able to intercept calls to Context.SaveChanges(), as I then iterate through Context.ChangeTracker.Entries() and log these to my AuditLog.

I need to be able to do this on the DbContext itself as I both have the possibility to fetch a DbContext transiently directly and PerWebRequest through my UnitOfWork.

I have tried to address this through a decorator, which isn't working for me!

public class AuditLogSaveChangesInKNContextDecorator : DbContext
{
    DbContext _context;
    IHandleCommand<AddAuditLogEntriesFromTrackedChangesAndSaveChangesCommand> _handler;

    public AuditLogSaveChangesInKNContextDecorator(DbContext context,
        IHandleCommand<AddAuditLogEntriesFromTrackedChangesAndSaveChangesCommand> handler)
    {
        _context = context;
        _handler = handler;
    }

    public override int SaveChanges()
    {
        var changes = base.SaveChanges();

        _handler.Handle(new AddAuditLogEntriesFromTrackedChangesAndSaveChangesCommand { 
            Context = _context 
        });

        return changes;
    }
}

Any Ideas?

2

There are 2 best solutions below

0
On BEST ANSWER

What you are doing seems quite right to me. The only way to 'intercept' the call to SaveChanges is by overriding that method; it is virtual. For instance:

public class MyDbContext : DbContext
{
    public event Action<MyDbContext> SavingChanges = _ => { };

    public override int SaveChanges()
    {
        // Notify objects that want to know, that we are gonna save some stuff
        this.SavingChanges(this);

        // Call the actual SaveChanges method to apply the changes to the database
        return base.SaveChanges();
    }
}

By using an event, we can add any behavior (dependency injection) to the MyDbContext without that context having to know about it. For instance:

container.RegisterPerWebRequest<MyDbContext>(() =>
{
    var context = new MyDbContext();
    context.SavingChanges += UpdateEntities;
    return context;
});

private static void UpdateEntities(MyDbContext db)
{
    var addedEntities =
        from entry in db.ChangeTracker.Entries()
        where entry.State == EntityState.Added
        select entry.Entity as IEntity;

    db.AuditTrailEntries.AddRange(
        from entity in addedEntities
        select new AuditTrailEntry 
        { 
            EntityId = entity.Id, 
            Type = entity.GetType().Name 
        });
}
0
On

You're calling SaveChanges() on the wrong DbContext.

Instead of :

var changes = base.SaveChanges();

You should be using:

var changes = _context.SaveChanges();

Since _context is the DbContext that you're trying to track, you need to do the save on that one, instead of on your decorator object.