Audit.NET Entity Framework适配器:嵌套属性未被记录问题
Audit.NET嵌套属性审计字段为空问题排查与解决
问题描述
已为项目中的ElementoWorkflow模型配置Audit.NET,整体审计功能正常,但嵌套属性StatoOrigine对应的审计表字段ID_STATO_ORIGINE_007始终为Null。相关代码、配置及数据库结构如下:
模型代码
public class ElementoWorkflow : BaseObject<ElementoWorkflow, int> { public override int Id { get; set; } public required Stato StatoOrigine { get; set; } public required bool Finale { get; set; } = false; } public class Stato : ServiceRegistryObject { public override RegistryType Gruppo { get => RegistryType.Stato; set => throw new InvalidOperationException("Non è possibile modificare il Gruppo di appartenenza. Utilizzare la classe generica ServiceRegistryObject."); } } public class ServiceRegistryObject : ServiceBaseObject<ServiceRegistryObject, int> { public override int Id { get; set; } public virtual string? Codice { get; set; } public virtual RegistryType Gruppo { get; set; } } public abstract class ServiceBaseObject<TEntity, TKey> : BaseObject<TEntity, TKey> where TEntity : class { public abstract bool Annullato { get; set; } public abstract TKey Ordine { get; set; } } public void Configure(EntityTypeBuilder<ElementoWorkflow> builder) { builder .ToTable($"NAT_DASH_ELEMENTO_WORKFLOW_007") .HasKey(c => c.Id) .HasName("NAK_DASH_007"); builder.Property(b => b.Id) .HasColumnName("ID_007"); builder.Property<int>("IdStatoOrigine") .HasColumnName("ID_STATO_ORIGINE_007") .IsRequired(); builder.HasOne(c => c.StatoOrigine) .WithMany() .HasForeignKey("IdStatoOrigine") .HasPrincipalKey(g => g.Id) .IsRequired(); builder.Property(b => b.Finale) .HasColumnName("FINALE_007") .IsRequired(); }
Audit.NET配置代码
public DatabaseContext(DbContextOptions<DatabaseContext> options, ILoggerFactory loggerFactory, IHttpContextAccessor httpContextAccessor) : base(options) { _loggerFactory = loggerFactory; this.ChangeTracker.LazyLoadingEnabled = false; Audit.EntityFramework.Configuration.Setup() .ForContext<DatabaseContext>(config => config .IncludeEntityObjects() .ReloadDatabaseValues(true) .AuditEventType("DatabaseContext")) .UseOptOut() .IgnoreAny(t => t.Name.EndsWith("History")); Audit.Core.Configuration.Setup() .UseEntityFramework(ef => ef .AuditTypeExplicitMapper(m => m .Map<ElementoWorkflow, ElementoWorkflowAudit>() .AuditEntityAction<IAudit>((evt, entry, auditEntity) => { auditEntity.AuditData = DateTime.UtcNow; auditEntity.AuditUser = httpContextAccessor.HttpContext?.User.Claims.FirstOrDefault(c => c.Type.Contains(TipoClaim.matricola.ToString()))?.Value; auditEntity.AuditAction = entry.Action; }) ) );
审计实体及配置代码
internal class ElementoWorkflowAudit : IAudit { public int AuditId { get; set; } public string AuditAction { get; set; } public string? AuditUser { get; set; } public DateTime AuditData { get; set; } public int Id { get; set; } public Stato StatoOrigine { get; set; } public bool Finale { get; set; } = false; } public void Configure(EntityTypeBuilder<ElementoWorkflowAudit> builder) { builder .ToTable("NAT_DASH_AUD_ELEMENTO_WORKFLOW_007") .HasKey(e => e.AuditId) .HasName("NAK_DASH_AUD_ELEMENTO_WORKFLOW_007"); builder.Property(b => b.AuditId).HasColumnName("AuditId") .HasDefaultValueSql($"SYUNA{Costanti.CodiceApplicazione}.SEQ_{Costanti.CodiceApplicazione}__AUD.NEXTVAL"); builder.Property(b => b.AuditData); builder.Property(b => b.AuditAction); builder.Property(b => b.AuditUser); builder.Property(b => b.Id) .HasColumnName("ID_007"); builder.Property<int>("IdStatoOrigine") .HasColumnName("ID_STATO_ORIGINE_007"); builder.HasOne(c => c.StatoOrigine) .WithMany() .HasForeignKey("IdStatoOrigine") .HasPrincipalKey(g => g.Id); builder.Property(b => b.Finale) .HasColumnName("FINALE_007"); }
Oracle数据库审计表结构
CREATE TABLE NAT_DASH_AUD_ELEMENTO_WORKFLOW_007 ( "AuditId" NUMBER(9, 0) DEFAULT "SYUNADASH"."SEQ_DASH__AUD"."NEXTVAL" NOT NULL , "AuditAction" VARCHAR2(250 BYTE) , "AuditUser" VARCHAR2(250 BYTE) , "AuditData" DATE , ID_007 NUMBER(12, 0) , ID_STATO_ORIGINE_007 NUMBER(9, 0) , FINALE_007 NUMBER(1, 0) , CONSTRAINT NAK_DASH_AUD_ELEMENTO_WORKFLOW_007 PRIMARY KEY ( "AuditId" ) )
原因分析
- 影子属性未自动同步:原实体与审计实体均使用影子属性
IdStatoOrigine存储外键,但Audit.NET默认不会自动将原实体的影子属性值同步到审计实体的对应字段。 - 导航属性未加载:由于
ChangeTracker.LazyLoadingEnabled = false,若操作时未显式加载StatoOrigine导航属性,Audit.NET无法获取关联实体的ID,导致审计外键字段为空。
解决方案
方案一:手动填充审计实体的影子属性
在AuditEntityAction中,从原实体的影子属性或已加载的导航属性中获取外键值,手动设置到审计实体的影子属性:
.AuditEntityAction<IAudit>((evt, entry, auditEntity) => { auditEntity.AuditData = DateTime.UtcNow; auditEntity.AuditUser = httpContextAccessor.HttpContext?.User.Claims.FirstOrDefault(c => c.Type.Contains(TipoClaim.matricola.ToString()))?.Value; auditEntity.AuditAction = entry.Action; var elementoWorkflow = entry.Entity as ElementoWorkflow; if (elementoWorkflow != null) { // 优先从原实体影子属性获取值,无需依赖导航属性加载 var idStatoOrigine = entry.Context.Entry(elementoWorkflow).Property("IdStatoOrigine").CurrentValue; entry.Context.Entry(auditEntity).Property("IdStatoOrigine").CurrentValue = idStatoOrigine; } })
方案二:将审计实体的影子属性改为显式属性
修改ElementoWorkflowAudit添加显式外键属性,Audit.NET会自动匹配原实体的外键值,无需手动处理:
internal class ElementoWorkflowAudit : IAudit { public int AuditId { get; set; } public string AuditAction { get; set; } public string? AuditUser { get; set; } public DateTime AuditData { get; set; } public int Id { get; set; } // 添加显式外键属性 public int IdStatoOrigine { get; set; } public Stato StatoOrigine { get; set; } public bool Finale { get; set; } = false; }
同步更新审计实体的EF配置,绑定显式属性作为外键:
// 替换原影子属性配置 builder.Property(b => b.IdStatoOrigine) .HasColumnName("ID_STATO_ORIGINE_007"); builder.HasOne(c => c.StatoOrigine) .WithMany() .HasForeignKey(c => c.IdStatoOrigine) .HasPrincipalKey(g => g.Id);
方案三:启用Audit.NET导航属性包含配置
在Audit.NET的EF上下文配置中添加.IncludeNavigationProperties(),自动处理导航属性及关联外键(注意:会加载关联实体,可能影响性能):
Audit.EntityFramework.Configuration.Setup() .ForContext<DatabaseContext>(config => config .IncludeEntityObjects() .ReloadDatabaseValues(true) .IncludeNavigationProperties() .AuditEventType("DatabaseContext"))
推荐方案
优先选择方案二,显式外键属性的方式更直观,Audit.NET的映射逻辑可自动处理,避免手动维护影子属性的繁琐,同时降低导航属性未加载导致的问题。
内容的提问来源于stack exchange,提问作者gt.guybrush
相关产品推荐
相关产品推荐

