【问题标题】:Audit.NET Entity Framework Core - Related Entities managementAudit.NET Entity Framework Core - 相关实体管理
【发布时间】:2020-08-06 16:26:42
【问题描述】:

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

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

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

此时,我可以对独立的审计实体进行 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);
        }
    }

这是 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 以便能够查询审计数据库以获取分组审计数据?

这是 ID 为 #5 的 Scope 的审计跟踪。

Audit_Scope 表

这是 ScopeId #5 的作业的审计跟踪。

Audit_Job 表

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

谢谢, 马特奥

【问题讨论】:

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


    【解决方案1】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2018-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-16
      • 1970-01-01
      • 1970-01-01
      • 2018-03-24
      相关资源
      最近更新 更多