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

C# Audit.NET实体框架核心相关实体管理,c#,entity-framework-core,audit.net,C#,Entity Framework Core,Audit.net,我正在开发一个ASP.NET Core 3.1 web应用程序。我希望在数据库上持久化数据时添加审计跟踪/日志 我从中获得灵感,开始在测试项目中使用Audit.NET。 以下是我的目标(类似于相关的SO线程): 将审核记录存储在不同的数据库中:几乎使用额外的AppAuditDbContext完成 每种类型都有一个与已审核类型匹配的审核表(带有附加审核字段):Done通过额外的AppAuditDbContext上的反射 不需要维护单独的审计实体。变化 运营数据库和审计数据库应该是无缝的:通过对额外

我正在开发一个ASP.NET Core 3.1 web应用程序。我希望在数据库上持久化数据时添加审计跟踪/日志

我从中获得灵感,开始在测试项目中使用Audit.NET。
以下是我的目标(类似于相关的SO线程):

  • 将审核记录存储在不同的数据库中:几乎使用额外的
    AppAuditDbContext
    完成
  • 每种类型都有一个与已审核类型匹配的审核表(带有附加审核字段):Done通过额外的
    AppAuditDbContext
    上的反射
  • 不需要维护单独的审计实体。变化 运营数据库和审计数据库应该是无缝的:通过对额外的
    AppAuditDbContext
    和被审计实体上的
    数据注释进行反射来完成
    
  • 检索相关实体的审核数据:要做的事
  • 此时,我可以CRUD一个独立的已审核实体,并在审核数据库中检索正确的审核。
    但是,尽管我可以成功地删除父实体及其子实体,并获取父实体和子实体的审核数据,但我不知道如何从数据库中获取此类场景的分组审核数据。
    我尝试使用Audit.NET EntityFramework的
    EntityFrameworkEvent.TransactionId
    EntityFrameworkEvent.AmbientTransactionId
    ,但它们在DB上都是
    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; }
    }
    
    公共接口IAuditableEntity
    {
    [未映射]
    字符串审核操作{get;set;}
    [未映射]
    字符串AuditTransactionId{get;set;}
    [未映射]
    字符串AuditAmbientTransactionId{get;set;}
    }
    公共类作用域:IAuditableEntity
    {
    [关键]
    公共int Id{get;set;}
    公共字符串名称{get;set;}
    公共虚拟ICollection作业{get;set;}
    [未映射]
    字符串审核操作{get;set;}
    [未映射]
    字符串AuditTransactionId{get;set;}
    [未映射]
    字符串AuditAmbientTransactionId{get;set;}
    }
    公共类作业:IAuditableEntity
    {
    [关键]
    公共int Id{get;set;}
    public int ScopeId{get;set;}
    公共虚拟作用域{get;set;}
    [第128段]
    公共字符串名称{get;set;}
    [未映射]
    公共字符串审核操作{get;set;}
    [未映射]
    公共字符串AuditTransactionId{get;set;}
    [未映射]
    公共字符串AuditAmbientTransactionId{get;set;}
    }
    
    这是我的Audit.NET配置(来自Startup.cs)

    公共类启动
    {            
    public void配置服务(IServiceCollection服务)
    {
    services.AddDbContext
    审计范围表

    以下是ScopeId为5的作业的审核跟踪。

    审计工作表

    根据提供的数据,假设我想读取范围的删除审核(在本例中是审核范围表中的审核#9),包括其子作业的删除审核(在本例中是审核作业表中的审核#10)。我如何实现这一点

    谢谢,
    Matteo

    目前,我刚刚在实体中添加了一个自定义字段。 我在带有Guid的自定义操作中对其进行赋值

    // EF AuditEventId per scope
    Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
    {
        var id = Guid.NewGuid();
        scope.SetCustomField("AuditScopeId", id);
    });
    
    这样,与同一审核事件相关的
    Scope
    Job
    表记录将保持相同的
    AuditScopeId

    [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 AuditEventId per scope
    Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
    {
        var id = Guid.NewGuid();
        scope.SetCustomField("AuditScopeId", id);
    });