如何用Audit.EntityFramework将审计日志中外键替换为关联实体属性?
问题描述
在使用Audit.EntityFramework时,我希望将关联实体的名称填充到EventEntryChange对象的OriginalValue、NewValue属性,以及ColumnValues对象中。尝试通过Format配置实现:
Audit.EntityFramework.Configuration.Setup() .ForContext<AppDbContext>(config => config .ForEntity<Product>(prodConfig => { prodConfig.Format(x => x.ProductType, value => { // 从关联对象获取名称 var name = relatedObject?.Name; return name ?? value; }); }) );
但发现OriginalValue被替换为名称后,NewValue和ColumnValues又被重置回外键ID。原因是DbContextHelper中的UpdateAuditEvent方法会在EF保存操作完成后,重新更新审计事件的列值和外键:
/// <summary> /// Updates column values and primary keys on the Audit Event after the EF save operation completes. /// </summary> public void UpdateAuditEvent(EntityFrameworkEvent efEvent, IAuditDbContext context) { // Update PK and FK foreach (var efEntry in efEvent.Entries) { var entry = efEntry.Entry; efEntry.PrimaryKey = GetPrimaryKey(context.DbContext, entry); foreach (var pk in efEntry.PrimaryKey) { if (efEntry.ColumnValues.ContainsKey(pk.Key)) { efEntry.ColumnValues[pk.Key] = pk.Value; } } var fks = GetForeignKeys(context.DbContext, entry); foreach (var fk in fks) { if (efEntry.ColumnValues.ContainsKey(fk.Key)) { efEntry.ColumnValues[fk.Key] = fk.Value; } var change = efEntry.Changes?.FirstOrDefault(e => e.ColumnName == fk.Key); if (change != null) { change.NewValue = fk.Value; } } } // Update ConnectionId var clientConnectionId = TryGetClientConnectionId(context.DbContext); if (clientConnectionId != null) { efEvent.ConnectionId = clientConnectionId; } }
解决方案
可以通过两种方式实现需求:
1. 利用OnSaved事件二次修改审计内容
既然UpdateAuditEvent会在保存后重置外键值,我们可以在审计事件的OnSaved阶段重新替换成关联实体名称:
Audit.EntityFramework.Configuration.Setup() .ForContext<AppDbContext>(config => config .OnSaved(evt => { foreach (var entry in evt.EntityFrameworkEvent.Entries) { if (entry.EntityType == typeof(Product).Name) { var product = evt.GetEntityObject<Product>(); var productType = evt.DbContext.Set<ProductType>().Find(product.ProductTypeId); // 修改ColumnValues中的外键值为关联名称 if (entry.ColumnValues.ContainsKey("ProductTypeId")) { entry.ColumnValues["ProductTypeId"] = productType?.Name ?? entry.ColumnValues["ProductTypeId"]; } // 修改Changes中的OriginalValue和NewValue var typeChange = entry.Changes?.FirstOrDefault(c => c.ColumnName == "ProductTypeId"); if (typeChange != null) { // 替换原始值为对应名称 if (typeChange.OriginalValue is int originalId) { var originalType = evt.DbContext.Set<ProductType>().Find(originalId); typeChange.OriginalValue = originalType?.Name ?? originalId; } // 替换新值为对应名称 typeChange.NewValue = productType?.Name ?? typeChange.NewValue; } } } }) );
该方法在EF保存完成后,重新覆盖被框架重置的字段值,确保最终审计日志显示关联实体名称。
2. 添加自定义字段存储关联实体名称(推荐)
如果不想修改框架默认的ColumnValues和Change对象,可以在审计事件的CustomFields中新增字段存储关联名称,避免冲突:
Audit.EntityFramework.Configuration.Setup() .ForContext<AppDbContext>(config => config .ForEntity<Product>(prodConfig => { prodConfig.AuditCustomFields((entry, product) => new Dictionary<string, object> { ["ProductTypeName"] = product.ProductType?.Name ?? "Unknown" }); }) // 针对更新操作,添加原始关联名称 .OnSaving(evt => { foreach (var entry in evt.EntityFrameworkEvent.Entries) { if (entry.EntityType == typeof(Product).Name && entry.Action == "Update") { var originalProduct = entry.GetOriginalEntityObject<Product>(); var originalType = evt.DbContext.Set<ProductType>().Find(originalProduct.ProductTypeId); entry.CustomFields["OriginalProductTypeName"] = originalType?.Name ?? "Unknown"; } } }) );
这种方式不干扰框架默认逻辑,同时能在审计日志中清晰展示关联实体的名称信息,维护性更强。
内容的提问来源于stack exchange,提问作者Charles Boyung
相关产品推荐
相关产品推荐

