如何使用 Audit.Net - Audit.EntityFramework.Core 定位另一个数据库
How do I target another database with Audit.Net - Audit.EntityFramework.Core
我正在尝试从 Audit.Net repository 实施 Audit.EntityFramework.Core
包,但 运行 遇到了一些困难。我无法保存更改或定位到不同的数据库。我修改了我的 SaveChanges
和 SaveChangesAsync
函数来调用 Audit.Net
DbContextHelper
class 的保存函数,但我遗漏了一些东西。
有没有办法做到以下几点?
- 使用从我正在尝试审计的
DbContext
继承的审计 DbContext
定位另一个数据库来存储审计数据?
public class MyDbContext : DbContext {} //Types defined here
public class AuditDbContext : MyDbContext {} //This context stores audit data into a different DB
- 在建立全局连接时不需要类型和它的审核类型之间的映射? (我试图避免使用当前正在发生大量变化的模型为每种类型显式调用
AuditTypeMapper
)。
//MyDbContext has different connection string than AuditDbContext
Audit.Core.Configuration.Setup()
.UseEntityFramework(x => x
.UseDbContext<AuditDbContext>());
我试过类似于以下的代码,但在 SaveChanges
上出现运行时错误,表明没有设置模型。为 AuditDbContext
添加迁移没有帮助。
我明白我想做什么了。
我的设计目标是:
- 将审核记录存储在不同的数据库中
- 对每个类型进行审核 table 与审核类型相匹配(具有附加审核字段)
- 不需要维护单独的审计实体。操作数据库和审计数据库之间的更改应该是无缝的
我发现不起作用的是:
- 创建从我的操作 DbContext 继承的审计 DbContext 不起作用,因为关系、DBSet 和 ID 在审计数据库中无法以相同的方式处理。
- 使用 TypeBuilder 对操作类型的反射动态创建类型不起作用,因为 Audit.Net 在操作类型和审核类型之间转换对象以及从 CLR 类型转换为动态创建的类型失败。
- 混合具体类型和 EF Core 影子类型不起作用。
已采取的步骤
设置全局审计(在主要设置代码中)
//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;
}));
如果我的审核模型继承自基类 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; }
}
使用新的 DbContext
和 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);
}
}
- 为主数据库和审计数据库创建迁移。
有些人可能不需要达到这些水平,但我想分享一下,以防有人在使用 Audit.Net
时需要类似的东西
我正在尝试从 Audit.Net repository 实施 Audit.EntityFramework.Core
包,但 运行 遇到了一些困难。我无法保存更改或定位到不同的数据库。我修改了我的 SaveChanges
和 SaveChangesAsync
函数来调用 Audit.Net
DbContextHelper
class 的保存函数,但我遗漏了一些东西。
有没有办法做到以下几点?
- 使用从我正在尝试审计的
DbContext
继承的审计DbContext
定位另一个数据库来存储审计数据?public class MyDbContext : DbContext {} //Types defined here public class AuditDbContext : MyDbContext {} //This context stores audit data into a different DB
- 在建立全局连接时不需要类型和它的审核类型之间的映射? (我试图避免使用当前正在发生大量变化的模型为每种类型显式调用
AuditTypeMapper
)。//MyDbContext has different connection string than AuditDbContext Audit.Core.Configuration.Setup() .UseEntityFramework(x => x .UseDbContext<AuditDbContext>());
我试过类似于以下的代码,但在 SaveChanges
上出现运行时错误,表明没有设置模型。为 AuditDbContext
添加迁移没有帮助。
我明白我想做什么了。
我的设计目标是:
- 将审核记录存储在不同的数据库中
- 对每个类型进行审核 table 与审核类型相匹配(具有附加审核字段)
- 不需要维护单独的审计实体。操作数据库和审计数据库之间的更改应该是无缝的
我发现不起作用的是:
- 创建从我的操作 DbContext 继承的审计 DbContext 不起作用,因为关系、DBSet 和 ID 在审计数据库中无法以相同的方式处理。
- 使用 TypeBuilder 对操作类型的反射动态创建类型不起作用,因为 Audit.Net 在操作类型和审核类型之间转换对象以及从 CLR 类型转换为动态创建的类型失败。
- 混合具体类型和 EF Core 影子类型不起作用。
已采取的步骤
设置全局审计(在主要设置代码中)
//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; }));
如果我的审核模型继承自基类
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; } }
使用新的
创建了基于反射的审计模式DbContext
和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); } }
- 为主数据库和审计数据库创建迁移。
有些人可能不需要达到这些水平,但我想分享一下,以防有人在使用 Audit.Net
时需要类似的东西