Аудит. NET Entity Framework Core - Управление связанными объектами - PullRequest
1 голос
/ 23 апреля 2020

Я работаю над ASP. NET Core 3.1 веб-приложением. Я хотел бы добавить контрольные журналы / журналы при сохранении данных в базе данных.

Я черпал вдохновение из этого SO-ответа , чтобы начать работу с Audit. NET в тестовом проекте.
Вот мои цели (аналогично связанной теме SO):

  1. Хранить записи аудита в другой базе данных: Почти выполнено с использованием дополнительных AppAuditDbContext;
  2. Иметь таблицу аудита для каждого типа, соответствующего проверяемому типу (с дополнительные поля аудита): Завершено с помощью размышлений о дополнительных AppAuditDbContext;
  3. Не требует обслуживания отдельных объектов аудита. Изменения между оперативной БД и аудиторской БД должны быть беспрепятственными: Готово путем отражения дополнительных AppAuditDbContext и DataAnnotations в проверяемых объектах;
  4. Извлечение проверенных данных для связанных объектов: TO DO

На этом этапе я могу CRUD автономный объект аудита и получить правильный аудит в базе данных Audit.
Однако, хотя я могу успешно удалить родительский объект с его потомками и получением данных аудита для родительских и дочерних объектов, я не могу понять, как получить сгруппированные данные аудита из БД для такого сценария.
Я пытался использовать Audit. NET EntityFramework's EntityFrameworkEvent.TransactionId и EntityFrameworkEvent.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 config (из 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's 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, чтобы иметь возможность запрашивать базу данных Audit для получения сгруппированных данных аудита?

Вот контрольный журнал для Scope с Id # 5.
Audit trail for Scope
Таблица Audit_Scope

Вот Audit след для задания с ScopeId # 5.
Audit trail for Job
таблица Audit_Job

Учитывая предоставленные данные, скажем, я хочу прочитать удалить аудит Scope (в данном случае AuditId # 9 из таблицы Audit_Scope), включая удаление аудита его дочерних заданий (в данном случае AuditId # 10 из таблицы Audit_Job). Как мне этого добиться?

Спасибо, Маттео

1 Ответ

0 голосов
/ 30 апреля 2020

В данный момент я только что добавил свое поле к своим сущностям. Я оцениваю его в настраиваемом действии с помощью Guid.

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

Таким образом, в обеих записях таблицы Scope и Job, относящихся к одному и тому же событию аудита, будет содержаться одно и то же значение AuditScopeId.

...