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

时间:2020-04-23 11:02:32

标签: entity-framework-core audit.net

我正在使用ASP.NET Core 3.1 Web应用程序。在将数据持久存储在数据库中时,我想添加审计跟踪/日志。

我从this SO answer得到启发,开始在一个测试项目中使用Audit.NET。
这是我的目标(类似于相关的SO线程):

  1. 将审核记录存储在另一个数据库中:几乎完成,使用额外的AppAuditDbContext;
  2. 每种类型都有与已审核类型匹配的审核表(带有其他审核字段):完成(通过对多余的AppAuditDbContext进行反思;
  3. 不需要维护单独的审计实体。之间的变化 业务数据库和审计数据库应该是无缝的:完成,通过对被审计实体的额外AppAuditDbContextDataAnnotations进行反思;
  4. 检索相关实体的审核数据:要做

这时,我可以对一个独立的审计实体进行CRUD,并在Audit数据库上检索正确的审计。
但是,尽管我可以成功删除带有其子级的父实体并获取父子实体的审计数据,但是我无法弄清楚在这种情况下如何从数据库中获取分组审计数据。
我尝试使用Audit.NET EntityFramework的EntityFrameworkEvent.TransactionIdEntityFrameworkEvent.AmbientTransactionId,但是它们都在数据库中null

这是我的POCO

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);
        }
    }

这是作用域的“删除”控制器操作。

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以查询Audit数据库以获取分组的审核数据?

这是ID为5的合并范围的审核跟踪。
Audit trail for Scope
Audit_Scope表

这是具有ScopeId#5的作业的审核记录。
Audit trail for Job
Audit_Job表

鉴于提供的数据,假设我要读取范围的删除审核(在本例中为Audit_Scope表中的AuditId#9),包括对其子Jobs的删除审核(在本例中为AuditID#10从Audit_Job表)。我该如何实现?

谢谢, Matteo

1 个答案:

答案 0 :(得分:2)

此刻,我刚刚向我的实体添加了一个自定义字段。 我非常重视使用Guid进行自定义操作的方法。

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

通过这种方式,与同一审核事件相关的ScopeJob表记录都将具有相同的AuditScopeId值。