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?
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
});
}