Audit.NET实体框架核心 - 关联实体管理

5

我正在开发一个ASP.NET Core 3.1的Web应用程序。我想在将数据持久化到数据库时添加审计记录/日志。

我从这个SO答案中获取了灵感,开始在测试项目中使用Audit.NET。
以下是我的目标(与相关的SO主题类似):

  1. 将审计记录存储在不同的数据库中:几乎完成,使用额外的AppAuditDbContext
  2. 对于每种类型,都有一个匹配审核类型的审计表(具有附加审计字段):已完成,通过对额外的AppAuditDbContext进行反射实现;
  3. 不需要维护单独的审计实体。操作数据库和审计数据库之间的更改应无缝:通过对额外的AppAuditDbContext和受审计实体上的DataAnnotations进行反射,已完成
  4. 针对相关实体检索审计数据:待完成

此时,我可以对一个独立的经过审计的实体进行创建、读取、更新和删除,并能够从审计数据库中检索到正确的审计记录。
但是,虽然我可以成功地删除带有子项的父实体并获取父实体和子实体的审计数据,但是我无法弄清楚如何从数据库中获取分组的审计数据以应对这种情况。
我尝试使用Audit.NET EntityFramework的EntityFrameworkEvent.TransactionIdEntityFrameworkEvent.AmbientTransactionId,但它们在数据库中都为null

以下是我的POCOs:

public interface IAuditableEntity
{
    [NotMapped]
    string AuditAction { get; set; }

    [NotMapped]
    string AuditTransactionId { get; set; }

    [NotMapped]
    string AuditAmbientTransactionId { get; set; }
}

public class Scope : IAuditableEntity
{
    [Key]
    public int Id {get;set;}

    public string Name { get; set; }

    public virtual ICollection<Job> Jobs { get; set; }

    [NotMapped]
    string AuditAction { get; set; }

    [NotMapped]
    string AuditTransactionId { get; set; }

    [NotMapped]
    string AuditAmbientTransactionId { get; set; }
}

public class Job : IAuditableEntity
{
    [Key]
    public int Id {get;set;}

    public int ScopeId { get; set; }
    public virtual Scope Scope { get; set; }

    [StringLength(128)]
    public string Name { get; set; }

    [NotMapped]
    public string AuditAction { get; set; }

    [NotMapped]
    public string AuditTransactionId { get; set; }

    [NotMapped]
    public string AuditAmbientTransactionId { get; set; }
}

这是我的Audit.NET配置(在Startup.cs中)

public class Startup
    {            
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

            services.AddDbContext<AppAuditDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("AuditConnection")));

            #region Audit.NET

            var auditDbContextOptions = new DbContextOptionsBuilder<AppAuditDbContext>()
                .UseSqlServer(Configuration.GetConnectionString("AuditConnection"))
                .Options;

            Audit.Core.Configuration.Setup()
                .UseEntityFramework(x => x
                .UseDbContext<AppAuditDbContext>(auditDbContextOptions)
                .AuditTypeNameMapper(typeName =>
                {
                    return typeName;
                }).AuditEntityAction<IAuditableEntity>((ev, ent, auditEntity) =>
                {
                    var entityFrameworkEvent = ev.GetEntityFrameworkEvent();
                    if (entityFrameworkEvent == null) return;

                    auditEntity.AuditTransactionId = entityFrameworkEvent.TransactionId;
                    auditEntity.AuditAmbientTransactionId = entityFrameworkEvent.AmbientTransactionId;
                    auditEntity.AuditAction = ent.Action;
                }));

            #endregion


            services.AddControllersWithViews();
        }

        // other stuff..
    }

这是经过审计的上下文。

[AuditDbContext(IncludeEntityObjects = true)]
    public class ApplicationDbContext : AuditDbContext
    {

        public ApplicationDbContext([NotNullAttribute] DbContextOptions<ApplicationDbContext> options) : base(options)
        {
        }

        public DbSet<Scope> Scopes { get; set; }
        public DbSet<Job> Jobs { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Scope>().ToTable("Scope");

            modelBuilder.Entity<Job>().ToTable("Job");
        }

        public override int SaveChanges()
        {
            return base.SaveChanges();
        }

        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
        {
            return await base.SaveChangesAsync(cancellationToken);
        }
    }

这是 Scope 的 Delete 控制器操作。

public class ScopeController : Controller
    {
        private readonly ApplicationDbContext _context;

        public ScopeController(ApplicationDbContext context)
        {
            _context = context;
        }

        // Other controller actions...

        // POST: Scope/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> DeleteConfirmed(int id)
        {
            var scope = await _context.Scopes.Include(s => s.Jobs).SingleOrDefaultAsync(w => w.Id == id);            
            // using _context.Scopes.FindAsync(id) instead does delete the children Jobs without auditing it
            _context.Scopes.Remove(scope);
            await _context.SaveChangesAsync().ConfigureAwait(false);
            return RedirectToAction(nameof(Index));
        }
    }

这个控制器操作从 EF 视角下起作用。它还审核了父项和子项的删除操作,但我不知道如何将子级审核记录与父级审核记录关联起来。 我应该在代码中的哪里添加 AuditScope 呢?请问,我该如何配置 Audit.NET 以便能够查询审计数据库以获取分组审计数据?

以下是编号为 #5 的 Scope 的审计跟踪信息。
Audit trail for Scope
Audit_Scope 表

以下是 ScopeId 为 #5 的 Job 的审计跟踪信息。
Audit trail for Job
Audit_Job 表

根据提供的数据,假设我想读取 Scope 的删除审计记录(在此示例中为 AuditId #9,来自 Audit_Scope 表),包括其子 Job 的删除审计记录(在此示例中为 AuditId #10,来自 Audit_Job 表)。我该如何实现呢?

谢谢, Matteo

1个回答

2

目前,我只是在我的实体中添加了一个自定义字段。我使用Guid在自定义操作中对其进行评估。

// EF AuditEventId per scope
Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
{
    var id = Guid.NewGuid();
    scope.SetCustomField("AuditScopeId", id);
});

这样,与同一审计事件相关的ScopeJob表记录将都持有相同的AuditScopeId值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接