I'm overriding DbContext.SaveChanges() in order to implement database auditing within my application.
My DbContext class looks like this:
public override int SaveChanges()
{
throw new InvalidOperationException("User Id must be provided for auditing purposes.");
}
public int SaveChanges(bool suppressAudit)
{
if (suppressAudit)
return base.SaveChanges();
else
throw new InvalidOperationException("User Id must be provided for auditing purposes.");
}
public int SaveChanges(int userId)
{
foreach(var entity in ChangeTracker.Entries().Where(p=>p.State == EntityState.Added || p.State == EntityState.Deleted || p.State == EntityState.Modified))
{
foreach(var auditEntry in GetAuditRecordsForChange(entity,userId))
{
Audit.Add(auditEntry);
}
}
return base.SaveChanges();
}
and during "normal" operation of my application it seems to be working perfectly fine (at least, so far).
However, the problem comes when I want to recreate my database using an initializer, which looks like this:
public class ABS4Initializer : DropCreateDatabaseAlways<MyContext>
{
protected override void Seed(MyContext context)
{
var users = BuildUserData();
users.ForEach(u => context.Users.AddOrUpdate(u));
context.SaveChanges(true);
}
}
Despite me using the context.SaveChanges(true)
call in the seed, something, somewhere, is calling context.SaveChanges()
(and as such, the expected exception is thrown)
I suspect it surrounds the creation of the database / tables, but I can't find out where.
Sticking a break point on SaveChanges
just shows that "External Code" is making the call:
I'm assuming I've missed some override somewhere, or something even simpler/stupider.
Any clues?
(For reference, I'm trying to implement something similar to that shown as the top-voted answer on this question: how to create an audit trail with Entity framework 5 and MVC 4)
EDIT As requested in the comments, here is a FULL dbContext class that produces the same result:
public class DemoContext : DbContext
{
public DemoContext() : base("DemoContext")
{
}
public DbSet<User> Users { get; set; }
public DbSet<AuditLog> Audit { get; set; }
public override int SaveChanges()
{
throw new InvalidOperationException("User Id must be provided for auditing purposes.");
}
public int SaveChanges(bool suppressAudit)
{
if (suppressAudit)
return base.SaveChanges();
else
throw new InvalidOperationException("User Id must be provided for auditing purposes.");
}
public int SaveChanges(int userId)
{
foreach (var entity in ChangeTracker.Entries().Where(p => p.State == EntityState.Added || p.State == EntityState.Deleted || p.State == EntityState.Modified))
{
foreach (var auditEntry in GetAuditRecordsForChange(entity, userId))
{
Audit.Add(auditEntry);
}
}
return base.SaveChanges();
}
private List<AuditLog> GetAuditRecordsForChange(DbEntityEntry dbEntity, int userId)
{
return new List<AuditLog>();
}
}
App.config
<entityFramework>
<contexts>
<context type="DAL.DAL.DemoContext, DAL">
<databaseInitializer type="DAL.DAL.ABS4Initializer, DAL"/>
</context>
</contexts>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="mssqllocaldb" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>