You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Audit.EntityFramework.Core无需创建审计对象存储多表变更

问题描述

我正在新应用中使用Audit.EntityFramework.Core库搭建审计日志。按照指南配置了DbContext(代码如下),但我理解下来似乎需要为每个要记录的实体在领域层创建特定的审计对象,这在DDD视角下毫无意义,且找不到这类对象的示例。请问是否可以像NHibernate的审计日志库Envers那样,直接告知该库要存储日志的表名?

public class DatabaseContext : AuditDbContext, IDatabaseContext
{
    private readonly ILoggerFactory _loggerFactory;

    #region Ctor
    public DatabaseContext(DbContextOptions<DatabaseContext> options, ILoggerFactory loggerFactory)
     : base(options)
    {
        _loggerFactory = loggerFactory;
        //this.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        this.ChangeTracker.LazyLoadingEnabled = false;

        Audit.EntityFramework.Configuration.Setup()
            .ForContext<DatabaseContext>(config => config
                .IncludeEntityObjects()
                .AuditEventType("{context}:{database}"))
            .UseOptOut()
                .IgnoreAny(t => t.Name.EndsWith("History"));


        Audit.Core.Configuration.Setup()
        .UseEntityFramework(x => x
        .UseDbContext<DatabaseContext>()
        .AuditTypeNameMapper(typeName => "Audit_" + typeName)
        .AuditEntityAction((ev, ent, auditEntity) =>
        {
            // auditEntity is object
            ((dynamic)auditEntity).AuditDate = DateTime.UtcNow;
        }));
    }
    #endregion
    #region DbSet
    public DbSet<ParametroConfigurazione> ParametriConfigurazione { get; set; }
    #endregion
    #region Methods
    public Task<int> SaveChangesAsync() => base.SaveChangesAsync();
    #endregion

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ParametroConfigurazioneConfiguration).Assembly);


        base.OnModelCreating(modelBuilder);
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseLoggerFactory(_loggerFactory)
            .AddInterceptors(new AuditCommandInterceptor()
            {
                ExcludeNonQueryEvents = true,
                AuditEventType = "DatabaseContext",
                IncludeReaderResults = true
            })
            ;
    }

}
解决方案

完全可以不用为每个领域实体创建单独的审计对象,有两种核心方式实现类似Envers的表名指定逻辑:

1. 使用单一通用审计表存储所有实体变更

如果你想把所有实体的审计日志统一存在一张表中,不需要创建任何审计实体,只需自定义审计数据的存储逻辑:

  • 先定义一个通用的审计日志实体(只需一次,无需对应每个领域实体):
public class AuditLog
{
    public int Id { get; set; }
    public string EntityType { get; set; }
    public string EntityId { get; set; }
    public string Action { get; set; } // Create/Update/Delete
    public DateTime AuditDate { get; set; }
    public string Changes { get; set; } // 存储变更的JSON数据
    public string UserId { get; set; } // 可选:操作人ID
}
  • 在DbContext中添加对应的DbSet:
public DbSet<AuditLog> AuditLogs { get; set; }
  • 修改Audit.Core的配置,替换默认的EF存储为自定义逻辑,直接将审计事件写入通用表:
Audit.Core.Configuration.Setup()
    .UseCustomAction(scope =>
    {
        var auditEvent = scope.Event;
        // 遍历每个变更的实体
        foreach (var entity in auditEvent.Entries)
        {
            var auditLog = new AuditLog
            {
                EntityType = entity.EntityType.Name,
                EntityId = entity.PrimaryKey.First().Value.ToString(),
                Action = entity.Action,
                AuditDate = DateTime.UtcNow,
                Changes = JsonSerializer.Serialize(entity.Changes),
                UserId = auditEvent.Environment.UserName // 按需获取操作人
            };
            // 将日志添加到当前DbContext
            ((DatabaseContext)scope.GetDbContext<DatabaseContext>()).AuditLogs.Add(auditLog);
        }
        // 保存审计日志
        ((DatabaseContext)scope.GetDbContext<DatabaseContext>()).SaveChanges();
    });

这种方式下,你只需要维护一张AuditLogs表,所有实体的变更都存在这里,完全不需要为每个领域实体创建审计对象,符合DDD领域层的纯净性要求。

2. 自动生成审计表(无需手动创建审计实体)

如果你还是想为每个实体生成独立的审计表,但不想手动创建审计实体,可以利用库的动态实体功能:

修改Audit.EntityFramework的配置,指定审计表的命名规则,同时启用动态审计实体:

Audit.EntityFramework.Configuration.Setup()
    .ForContext<DatabaseContext>(config => config
        .IncludeEntityObjects()
        .AuditEventType("{context}:{database}")
        .AuditTableNameMapper(entityType => $"Audit_{entityType.Name}")) // 直接指定审计表名
    .UseOptOut()
    .IgnoreAny(t => t.Name.EndsWith("History"));

Audit.Core.Configuration.Setup()
    .UseEntityFramework(x => x
        .UseDbContext<DatabaseContext>()
        .UseDynamicAuditEntities() // 使用动态审计实体,无需手动创建
        .AuditEntityAction((ev, ent, auditEntity) =>
        {
            ((dynamic)auditEntity).AuditDate = DateTime.UtcNow;
            // 可添加其他通用字段,比如操作人
            ((dynamic)auditEntity).UserId = ev.Environment.UserName;
        }));

这种方式下,库会自动为每个被审计的实体生成对应的动态审计类型,EF会根据AuditTableNameMapper指定的规则创建表(比如Audit_ParametroConfigurazione),你不需要在领域层创建任何审计对象,完全由库自动处理。

注意事项

  • 如果使用动态审计实体,需要确保EF Core能正确生成表结构,可以通过迁移命令生成对应的表。
  • 通用审计表的方式更适合DDD,因为审计日志属于基础设施层的关注点,不应侵入领域层,用单一表存储可以避免领域层出现与业务无关的审计实体。

内容的提问来源于stack exchange,提问作者gt.guybrush

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 01:53:22