如何将代码优先与 EntityFramework-Plus 的审计功能结合使用?
How to use code-first with EntityFramework-Plus' audit feature?
我正在尝试设置 EntityFramework Plus' Audit Auto-Save feature,但看起来我卡在了一些非常愚蠢的地方。我正在遵循 "Saving automatically by overriding SaveChanges & SaveChangesAsync" 路径,但我正在尝试使用代码优先,因为我将使用它的项目已经 运行ning 像这样已经有一段时间了。话虽如此,我的 DbContext 看起来像这样:
public class CadastralDbContext : DbContext
{
public CadastralDbContext(DbContextOptions<CadastralDbContext> options) : base(options) { }
static CadastralDbContext()
{
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
(context as CadastralDbContext).AuditEntries.AddRange(audit.Entries);
}
public DbSet<AuditEntry> AuditEntries { get; set; }
public DbSet<AuditEntryProperty> AuditEntryProperties { get; set; }
//Ommited my DbSets
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(CadastralDbContext).Assembly);
/*** Ignore these for now ***/
//modelBuilder.Entity<AuditEntry>().Ignore(x => x.Properties);
//modelBuilder.Entity<AuditEntryProperty>().Ignore(x => x.Parent);
}
public override int SaveChanges()
{
var audit = new Audit();
audit.PreSaveChanges(this);
var rowAffecteds = base.SaveChanges();
audit.PostSaveChanges();
if (audit.Configuration.AutoSavePreAction != null)
{
audit.Configuration.AutoSavePreAction(this, audit);
base.SaveChanges();
}
return rowAffecteds;
}
public async Task<int> SaveChangesAsync()
{
return await SaveChangesAsync(CancellationToken.None);
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
var audit = new Audit();
audit.PreSaveChanges(this);
var rowAffecteds = await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
audit.PostSaveChanges();
if (audit.Configuration.AutoSavePreAction != null)
{
audit.Configuration.AutoSavePreAction(this, audit);
await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
return rowAffecteds;
}
}
}
基本上,教程中添加的 DbSet<AuditEntry>
和 DbSet<AuditEntryProperty>
是来自框架本身的 类。检查这些元数据,我们有:
//
// Summary:
// An audit entry.
public class AuditEntry
{
//
// Summary:
// Gets or sets the object state entry.
[NotMapped]
public object Entity;
//
// Summary:
// Gets or sets the object state entry.
[NotMapped]
public EntityEntry Entry;
//
// Summary:
// Gets or sets the parent.
public Audit Parent;
public AuditEntry();
//
// Summary:
// Gets or sets the identifier of the audit entry.
[Column(Order = 0)]
public int AuditEntryID { get; set; }
//
// Summary:
// Gets or sets who created this object.
[Column(Order = 5)]
[MaxLength(255)]
public string CreatedBy { get; set; }
//
// Summary:
// Gets or sets the the date of the changes.
[Column(Order = 6)]
public DateTime CreatedDate { get; set; }
//
// Summary:
// Gets or sets the name of the entity set.
[Column(Order = 1)]
[MaxLength(255)]
public string EntitySetName { get; set; }
//
// Summary:
// Gets or sets the name of the entity type.
[Column(Order = 2)]
[MaxLength(255)]
public string EntityTypeName { get; set; }
//
// Summary:
// Gets or sets the properties.
public List<AuditEntryProperty> Properties { get; set; }
//
// Summary:
// Gets or sets the entry state.
[Column(Order = 3)]
public AuditEntryState State { get; set; }
//
// Summary:
// Gets or sets the name of the entry state.
[Column(Order = 4)]
[MaxLength(255)]
public string StateName { get; set; }
}
和
//
// Summary:
// An audit entry property.
public class AuditEntryProperty
{
//
// Summary:
// Gets or sets the new value audited.
[NotMapped]
public PropertyEntry PropertyEntry;
public object NewValue;
public object OldValue;
public AuditEntryProperty();
//
// Summary:
// Gets or sets the name of the property internally.
[NotMapped]
public string InternalPropertyName { get; set; }
//
// Summary:
// Gets or sets a value indicating whether OldValue and NewValue is set.
[NotMapped]
public bool IsValueSet { get; set; }
//
// Summary:
// Gets or sets the name of the relation audited.
[Column(Order = 2)]
[MaxLength(255)]
public string RelationName { get; set; }
//
// Summary:
// Gets or sets the name of the property audited.
[Column(Order = 3)]
[MaxLength(255)]
public string PropertyName { get; set; }
//
// Summary:
// Gets or sets the parent.
public AuditEntry Parent { get; set; }
//
// Summary:
// Gets or sets the identifier of the audit entry property.
[Column(Order = 0)]
public int AuditEntryPropertyID { get; set; }
//
// Summary:
// Gets or sets the new value audited formatted.
[Column("NewValue", Order = 5)]
public string NewValueFormatted { get; set; }
//
// Summary:
// Gets or sets the identifier of the audit entry.
[Column(Order = 1)]
public int AuditEntryID { get; set; }
//
// Summary:
// Gets or sets the old value audited formatted.
[Column("OldValue", Order = 4)]
public string OldValueFormatted { get; set; }
}
除了两个属性外,它看起来还不错:public List<AuditEntryProperty> Properties { get; set; }
和 public AuditEntry Parent { get; set; }
。由于它们未标记为 virtual
,因此添加迁移将失败。我尝试了一种解决方法,只是想看看是否可以让它生成 tables,我确实成功了(那些行之前评论过):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//...
modelBuilder.Entity<AuditEntry>().Ignore(x => x.Properties);
modelBuilder.Entity<AuditEntryProperty>().Ignore(x => x.Parent);
}
这似乎禁用了两个 table 都具有的 PrimaryKey-ForeignKey 关系,它们是在框架本身内部设置的,因为没有迹象表明我应该手动执行此操作。我什至尝试 运行 脚本只是为了看看会发生什么,结果是灾难性的:
CREATE INDEX [IX_AuditEntryID] ON [dbo].[AuditEntryProperties]([AuditEntryID])
GO
ALTER TABLE [dbo].[AuditEntryProperties]
ADD CONSTRAINT [FK_dbo.AuditEntryProperties_dbo.AuditEntries_AuditEntryID]
FOREIGN KEY ([AuditEntryID])
REFERENCES [dbo].[AuditEntries] ([AuditEntryID])
ON DELETE CASCADE
GO
插入时出现以下 SQL 错误:String or binary data would be truncated
。所以我只是回滚到以前的状态,框架有一个“50% 的输出”,因为它会在用户请求时将记录保存到 AuditEntry table(它保存 table 等数据)插入、更新或删除操作,但 AuditEntryProperties(新值、旧值、列)中不会保留任何内容,除了那些被忽略的属性之外,我想不出任何其他原因。
我想我可以覆盖 AuditEntry 和 AuditEntryProperties,但这听起来像是一个大而愚蠢的解决方法。我不是数据库专家,我在这里缺少什么?
编辑:忘记添加迁移代码:
migrationBuilder.CreateTable(
name: "AuditEntries",
columns: table => new
{
AuditEntryID = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
CreatedBy = table.Column<string>(maxLength: 255, nullable: true),
CreatedDate = table.Column<DateTime>(nullable: false),
EntitySetName = table.Column<string>(maxLength: 255, nullable: true),
EntityTypeName = table.Column<string>(maxLength: 255, nullable: true),
State = table.Column<int>(nullable: false),
StateName = table.Column<string>(maxLength: 255, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AuditEntries", x => x.AuditEntryID);
});
migrationBuilder.CreateTable(
name: "AuditEntryProperties",
columns: table => new
{
AuditEntryPropertyID = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
AuditEntryID = table.Column<int>(nullable: false),
PropertyName = table.Column<string>(maxLength: 255, nullable: true),
RelationName = table.Column<string>(maxLength: 255, nullable: true),
NewValue = table.Column<string>(nullable: true),
OldValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AuditEntryProperties", x => x.AuditEntryPropertyID);
});
编辑 2
尝试使用 Fluent API:
添加 FK
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(CadastralDbContext).Assembly);
modelBuilder.Entity<AuditEntryProperty>().HasOne<AuditEntry>(prop => prop.Parent).WithMany(a => a.Properties).HasForeignKey(prop => prop.AuditEntryID);
}
仍然无法执行迁移,因为这些属性不是虚拟的。
上创建了一个问题
你会在这里找到一个你可以尝试的项目,我建议你继续讨论我们的问题跟踪器,因为 Stack Overflow 不是解决此类问题的平台。
我正在尝试设置 EntityFramework Plus' Audit Auto-Save feature,但看起来我卡在了一些非常愚蠢的地方。我正在遵循 "Saving automatically by overriding SaveChanges & SaveChangesAsync" 路径,但我正在尝试使用代码优先,因为我将使用它的项目已经 运行ning 像这样已经有一段时间了。话虽如此,我的 DbContext 看起来像这样:
public class CadastralDbContext : DbContext
{
public CadastralDbContext(DbContextOptions<CadastralDbContext> options) : base(options) { }
static CadastralDbContext()
{
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
(context as CadastralDbContext).AuditEntries.AddRange(audit.Entries);
}
public DbSet<AuditEntry> AuditEntries { get; set; }
public DbSet<AuditEntryProperty> AuditEntryProperties { get; set; }
//Ommited my DbSets
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(CadastralDbContext).Assembly);
/*** Ignore these for now ***/
//modelBuilder.Entity<AuditEntry>().Ignore(x => x.Properties);
//modelBuilder.Entity<AuditEntryProperty>().Ignore(x => x.Parent);
}
public override int SaveChanges()
{
var audit = new Audit();
audit.PreSaveChanges(this);
var rowAffecteds = base.SaveChanges();
audit.PostSaveChanges();
if (audit.Configuration.AutoSavePreAction != null)
{
audit.Configuration.AutoSavePreAction(this, audit);
base.SaveChanges();
}
return rowAffecteds;
}
public async Task<int> SaveChangesAsync()
{
return await SaveChangesAsync(CancellationToken.None);
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
var audit = new Audit();
audit.PreSaveChanges(this);
var rowAffecteds = await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
audit.PostSaveChanges();
if (audit.Configuration.AutoSavePreAction != null)
{
audit.Configuration.AutoSavePreAction(this, audit);
await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
return rowAffecteds;
}
}
}
基本上,教程中添加的 DbSet<AuditEntry>
和 DbSet<AuditEntryProperty>
是来自框架本身的 类。检查这些元数据,我们有:
//
// Summary:
// An audit entry.
public class AuditEntry
{
//
// Summary:
// Gets or sets the object state entry.
[NotMapped]
public object Entity;
//
// Summary:
// Gets or sets the object state entry.
[NotMapped]
public EntityEntry Entry;
//
// Summary:
// Gets or sets the parent.
public Audit Parent;
public AuditEntry();
//
// Summary:
// Gets or sets the identifier of the audit entry.
[Column(Order = 0)]
public int AuditEntryID { get; set; }
//
// Summary:
// Gets or sets who created this object.
[Column(Order = 5)]
[MaxLength(255)]
public string CreatedBy { get; set; }
//
// Summary:
// Gets or sets the the date of the changes.
[Column(Order = 6)]
public DateTime CreatedDate { get; set; }
//
// Summary:
// Gets or sets the name of the entity set.
[Column(Order = 1)]
[MaxLength(255)]
public string EntitySetName { get; set; }
//
// Summary:
// Gets or sets the name of the entity type.
[Column(Order = 2)]
[MaxLength(255)]
public string EntityTypeName { get; set; }
//
// Summary:
// Gets or sets the properties.
public List<AuditEntryProperty> Properties { get; set; }
//
// Summary:
// Gets or sets the entry state.
[Column(Order = 3)]
public AuditEntryState State { get; set; }
//
// Summary:
// Gets or sets the name of the entry state.
[Column(Order = 4)]
[MaxLength(255)]
public string StateName { get; set; }
}
和
//
// Summary:
// An audit entry property.
public class AuditEntryProperty
{
//
// Summary:
// Gets or sets the new value audited.
[NotMapped]
public PropertyEntry PropertyEntry;
public object NewValue;
public object OldValue;
public AuditEntryProperty();
//
// Summary:
// Gets or sets the name of the property internally.
[NotMapped]
public string InternalPropertyName { get; set; }
//
// Summary:
// Gets or sets a value indicating whether OldValue and NewValue is set.
[NotMapped]
public bool IsValueSet { get; set; }
//
// Summary:
// Gets or sets the name of the relation audited.
[Column(Order = 2)]
[MaxLength(255)]
public string RelationName { get; set; }
//
// Summary:
// Gets or sets the name of the property audited.
[Column(Order = 3)]
[MaxLength(255)]
public string PropertyName { get; set; }
//
// Summary:
// Gets or sets the parent.
public AuditEntry Parent { get; set; }
//
// Summary:
// Gets or sets the identifier of the audit entry property.
[Column(Order = 0)]
public int AuditEntryPropertyID { get; set; }
//
// Summary:
// Gets or sets the new value audited formatted.
[Column("NewValue", Order = 5)]
public string NewValueFormatted { get; set; }
//
// Summary:
// Gets or sets the identifier of the audit entry.
[Column(Order = 1)]
public int AuditEntryID { get; set; }
//
// Summary:
// Gets or sets the old value audited formatted.
[Column("OldValue", Order = 4)]
public string OldValueFormatted { get; set; }
}
除了两个属性外,它看起来还不错:public List<AuditEntryProperty> Properties { get; set; }
和 public AuditEntry Parent { get; set; }
。由于它们未标记为 virtual
,因此添加迁移将失败。我尝试了一种解决方法,只是想看看是否可以让它生成 tables,我确实成功了(那些行之前评论过):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//...
modelBuilder.Entity<AuditEntry>().Ignore(x => x.Properties);
modelBuilder.Entity<AuditEntryProperty>().Ignore(x => x.Parent);
}
这似乎禁用了两个 table 都具有的 PrimaryKey-ForeignKey 关系,它们是在框架本身内部设置的,因为没有迹象表明我应该手动执行此操作。我什至尝试 运行 脚本只是为了看看会发生什么,结果是灾难性的:
CREATE INDEX [IX_AuditEntryID] ON [dbo].[AuditEntryProperties]([AuditEntryID])
GO
ALTER TABLE [dbo].[AuditEntryProperties]
ADD CONSTRAINT [FK_dbo.AuditEntryProperties_dbo.AuditEntries_AuditEntryID]
FOREIGN KEY ([AuditEntryID])
REFERENCES [dbo].[AuditEntries] ([AuditEntryID])
ON DELETE CASCADE
GO
插入时出现以下 SQL 错误:String or binary data would be truncated
。所以我只是回滚到以前的状态,框架有一个“50% 的输出”,因为它会在用户请求时将记录保存到 AuditEntry table(它保存 table 等数据)插入、更新或删除操作,但 AuditEntryProperties(新值、旧值、列)中不会保留任何内容,除了那些被忽略的属性之外,我想不出任何其他原因。
我想我可以覆盖 AuditEntry 和 AuditEntryProperties,但这听起来像是一个大而愚蠢的解决方法。我不是数据库专家,我在这里缺少什么?
编辑:忘记添加迁移代码:
migrationBuilder.CreateTable(
name: "AuditEntries",
columns: table => new
{
AuditEntryID = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
CreatedBy = table.Column<string>(maxLength: 255, nullable: true),
CreatedDate = table.Column<DateTime>(nullable: false),
EntitySetName = table.Column<string>(maxLength: 255, nullable: true),
EntityTypeName = table.Column<string>(maxLength: 255, nullable: true),
State = table.Column<int>(nullable: false),
StateName = table.Column<string>(maxLength: 255, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AuditEntries", x => x.AuditEntryID);
});
migrationBuilder.CreateTable(
name: "AuditEntryProperties",
columns: table => new
{
AuditEntryPropertyID = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
AuditEntryID = table.Column<int>(nullable: false),
PropertyName = table.Column<string>(maxLength: 255, nullable: true),
RelationName = table.Column<string>(maxLength: 255, nullable: true),
NewValue = table.Column<string>(nullable: true),
OldValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AuditEntryProperties", x => x.AuditEntryPropertyID);
});
编辑 2 尝试使用 Fluent API:
添加 FK protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(CadastralDbContext).Assembly);
modelBuilder.Entity<AuditEntryProperty>().HasOne<AuditEntry>(prop => prop.Parent).WithMany(a => a.Properties).HasForeignKey(prop => prop.AuditEntryID);
}
仍然无法执行迁移,因为这些属性不是虚拟的。
你会在这里找到一个你可以尝试的项目,我建议你继续讨论我们的问题跟踪器,因为 Stack Overflow 不是解决此类问题的平台。