I'm trying to implement the Audit.EntityFramework.Core
package from the Audit.Net repository but am running into some difficulty. I'm unable to save changes or target a different database. I've modified my SaveChanges
and SaveChangesAsync
function to call the Audit.Net
DbContextHelper
class's save functions but I'm missing something.
Is there a way to do the following?
DbContext
that inherits from the DbContext
I'm trying to audit?
public class MyDbContext : DbContext {} //Types defined here
public class AuditDbContext : MyDbContext {} //This context stores audit data into a different DB
AuditTypeMapper
explicitly for each type with a model that's currently undergoing a lot of change).
//MyDbContext has different connection string than AuditDbContext
Audit.Core.Configuration.Setup()
.UseEntityFramework(x => x
.UseDbContext<AuditDbContext>());
I've tried code that resembles the following but get runtime errors on SaveChanges
that indicate that there is no model set up. Adding a migration for the AuditDbContext
didn't help.
Set up Global Auditing (in main setup code)
//Global setup of Auditing
var auditDbCtxOptions = new DbContextOptionsBuilder<MyAuditDbContext>()
.UseSqlServer(options.AuditDbConnectionString)
.Options;
Audit.Core.Configuration.Setup()
.UseEntityFramework(x => x
.UseDbContext<MyAuditDbContext>(auditDbCtxOptions)
.AuditTypeNameMapper(typeName =>
{
return typeName;
})
.AuditEntityAction<AuditInfo>((ev, ent, auditEntity) =>
{
auditEntity.DatabaseAction = ent.Action;
}));
Had my audited models inherit from a baseclass AuditInfo
public abstract class AuditInfo
{
public DateTime Created { get; set; }
public DateTime? Updated { get; set; }
public string CreatedBy { get; set; }
public string UpdatedBy { get; set; }
[NotMapped] //This is not mapped on the operational DB
public string DatabaseAction { get; set; }
}
Created a reflection-based audit schema using a new DbContext
and OnModelCreating
public class MyAuditContext : DbContext
{
public MyAuditContext(DbContextOptions<MyAuditContext> options) : base(options)
{
}
private readonly Type[] AllowedTypes = new Type[]
{
typeof(bool),
typeof(int),
typeof(decimal),
typeof(string),
typeof(DateTime),
};
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
Console.WriteLine($"Generating dynamic audit model");
//Go through each of the types in Hsa.Engine.Data.Models
var asm = Assembly.GetExecutingAssembly();
var modelTypes = asm.GetTypes()
.Where(type => type.Namespace == "My.Data.Models.Namespace");
//Create an entity For each type get all the properties on the model
foreach(var model in modelTypes.Where(t => t.IsClass && !t.IsAbstract && t.BaseType == typeof(AuditInfo)))
{
Console.WriteLine($"Creating entity for {model.Name}");
var table = modelBuilder.Entity(model, entity =>
{
//Remove all types from base model, otherwise we get a bunch of noise about foreign keys, etc.
foreach(var prop in model.GetProperties())
{
entity.Ignore(prop.Name);
}
foreach(var prop in model.GetProperties().Where(p => AllowedTypes.Any(t => p.PropertyType.IsAssignableFrom(t))))
{
Console.WriteLine($" Adding field: {prop.Name} - Type: {prop.PropertyType.Name}");
//Create a typed field for each property, not including ID or foreign key annotations (do include field lengths)
var dbField = entity.Property(prop.PropertyType, prop.Name);
if(prop.PropertyType.IsEnum)
{
dbField.HasConversion<string>();
}
if(dbField.Metadata.IsPrimaryKey())
{
dbField.ValueGeneratedNever(); //Removes existing model primary keys for the audit DB
}
}
//Add audit properties
entity.Property<int>("AuditId").IsRequired().UseSqlServerIdentityColumn();
entity.Property<DateTime>("AuditDate").HasDefaultValueSql("getdate()");
entity.Property<string>("DatabaseAction"); //included on AuditInfo but NotMapped to avoid putting it on the main DB. Added here to ensure it makes it into the audit DB
entity.HasKey("AuditId");
entity.HasIndex("Id");
entity.ToTable("Audit_" + model.Name);
});
}
base.OnModelCreating(modelBuilder);
}
}
Some people may not need to go to these levels but I wanted to share in case anyone needed something similar when using Audit.Net