Я работаю над ASP. NET Core 3.1 веб-приложением. Я хотел бы добавить контрольные журналы / журналы при сохранении данных в базе данных.
Я черпал вдохновение из этого SO-ответа , чтобы начать работу с Audit. NET в тестовом проекте.
Вот мои цели (аналогично связанной теме SO):
- Хранить записи аудита в другой базе данных: Почти выполнено с использованием дополнительных
AppAuditDbContext
; - Иметь таблицу аудита для каждого типа, соответствующего проверяемому типу (с дополнительные поля аудита): Завершено с помощью размышлений о дополнительных
AppAuditDbContext
; - Не требует обслуживания отдельных объектов аудита. Изменения между оперативной БД и аудиторской БД должны быть беспрепятственными: Готово путем отражения дополнительных
AppAuditDbContext
и DataAnnotations
в проверяемых объектах; - Извлечение проверенных данных для связанных объектов: 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_Scope
Вот Audit след для задания с ScopeId # 5.
таблица Audit_Job
Учитывая предоставленные данные, скажем, я хочу прочитать удалить аудит Scope (в данном случае AuditId # 9 из таблицы Audit_Scope), включая удаление аудита его дочерних заданий (в данном случае AuditId # 10 из таблицы Audit_Job). Как мне этого добиться?
Спасибо, Маттео