c# - Audit.NET Entity Framework Core - 相关实体管理

标签 c# entity-framework-core audit.net

我正在开发 ASP.NET Core 3.1 Web 应用程序。我想在数据库上保存数据时添加审计跟踪/日志。

我的灵感来自 this SO answer开始在测试项目中使用 Audit.NET。
这是我的目标(类似于相关的 SO 线程):

  • 将审计记录存储在不同的数据库中:几乎使用额外的 AppAuditDbContext 完成;
  • 每个类型都有一个与审计类型匹配的审计表(带有额外的审计字段):完成 通过对额外的反射(reflection) AppAuditDbContext ;
  • 不需要维护单独的审计实体。之间的变化
    操作数据库和审计数据库应该是无缝的:完成 通过对额外的反射(reflection) AppAuditDbContextDataAnnotations关于被审计实体;
  • 检索相关实体的审计数据:待办事项

  • 此时,我可以 CRUD 一个独立的审计实体,并在审计数据库上检索正确的审计。
    但是,虽然我可以成功删除父实体及其子实体并获取父实体和子实体的审计数据,但我无法弄清楚如何从数据库中获取分组审计数据以用于此类场景。
    我尝试使用 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);
            }
        }
    

    这是范围的删除 Controller 操作。
    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));
            }
        }
    

    此 Controller 操作从 EF 的角度工作。它还审核父和子删除操作,但我不知道如何将子审核记录与父审核记录相关联
    我应该在代码中的某处添加一个 AuditScope 吗?请问,我如何配置 Audit.NET 以便能够查询审计数据库以获取分组审计数据?

    这是 ID 为 #5 的作用域的审计跟踪。
    Audit trail for Scope
    Audit_Scope 表

    这是具有 ScopeId #5 的作业的审计跟踪。
    Audit trail for Job
    Audit_Job 表

    鉴于提供的数据,假设我想读取范围的删除审计(在本例中为 Audit_Scope 表中的 AuditId #9),包括其子作业的删除审计(在本例中为 Audit_Job 表中的 AuditId #10) .我怎样才能做到这一点?

    谢谢,
    马泰奥

    最佳答案

    目前我刚刚为我的实体添加了一个自定义字段。
    我在带有 Guid 的自定义操作中重视它。

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

    这样我俩的ScopeJob与同一审计事件相关的表记录将保持相同 AuditScopeId值(value)。

    关于c# - Audit.NET Entity Framework Core - 相关实体管理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61385646/

    相关文章:

    c# - 如何在 Akka.NET 中使用 TestKit

    c# - 映射到同一个表的 EF CORE 多个属性

    Audit.Net:在不同数据提供者中保存的过程

    c# - 简单选择上的 LINQ NullReferenceException

    c# - 传递作为参数传入的谓词

    c# - 安全返回构造的 IDisposables 的最佳方法是什么?

    c# - 获取具有指定 ID 的行之后按字母顺序排列的行

    asp.net - Entity Framework - 对象 'PK_AspNetUserTokens' 依赖于列 'UserId'

    asp.net-core - ASP.NET Core 实体变更历史

    c# - 从 SQL Server 查询 C# 中的 JSON