using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using IRaCIS.Core.Domain.Models; using IRaCIS.Core.Infra.EFCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; public class AuditingInterceptor : ISaveChangesInterceptor { private readonly string _connectionString; private SaveChangesAudit _audit; public AuditingInterceptor(string connectionString) { _connectionString = connectionString; } #region SavingChanges public async ValueTask> SavingChangesAsync( DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) { _audit = CreateAudit(eventData.Context); await Task.CompletedTask; return result; } public InterceptionResult SavingChanges( DbContextEventData eventData, InterceptionResult result) { _audit = CreateAudit(eventData.Context); return result; } #endregion #region SavedChanges public int SavedChanges(SaveChangesCompletedEventData eventData, int result) { if (_audit.Entities.Count > 0) { var auditContext = eventData.Context as IRaCISDBContext; _audit.Succeeded = true; _audit.EndTime = DateTime.UtcNow.AddHours(8); auditContext.SaveChangesAudits.Add(_audit); auditContext.SaveChanges(); } return result; } public async ValueTask SavedChangesAsync( SaveChangesCompletedEventData eventData, int result, CancellationToken cancellationToken = default) { if (_audit.Entities.Count > 0) { var auditContext = eventData.Context as IRaCISDBContext; _audit.Succeeded = true; auditContext.SaveChangesAudits.Add(_audit); _audit.EndTime = DateTime.UtcNow.AddHours(8); await auditContext.SaveChangesAsync(); } return result; } #endregion #region SaveChangesFailed public void SaveChangesFailed(DbContextErrorEventData eventData) { using (var auditContext = new AuditContext(_connectionString)) { auditContext.Attach(_audit); _audit.Succeeded = false; _audit.EndTime = DateTime.UtcNow.AddHours(8); _audit.ErrorMessage = eventData.Exception.Message; auditContext.SaveChanges(); } } public async Task SaveChangesFailedAsync( DbContextErrorEventData eventData, CancellationToken cancellationToken = default) { using (var auditContext = new AuditContext(_connectionString)) { auditContext.Attach(_audit); _audit.Succeeded = false; _audit.EndTime = DateTime.UtcNow.AddHours(8); _audit.ErrorMessage = eventData.Exception.InnerException?.Message; await auditContext.SaveChangesAsync(cancellationToken); } } #endregion #region CreateAudit private static bool NeedAudit(EntityEntry entityEntry) { var type = entityEntry.Entity.GetType(); return type != typeof(EntityAudit) && type != typeof(SaveChangesAudit) && type != typeof(DicomSeries) && type != typeof(DicomInstance) && type != typeof(TrialAudit); } private static SaveChangesAudit CreateAudit(DbContext context) { context.ChangeTracker.DetectChanges(); var audit = new SaveChangesAudit { StartTime = DateTime.UtcNow.AddHours(8) }; foreach (var entry in context.ChangeTracker.Entries().Where(t => NeedAudit(t))) { var auditMessage = entry.State switch { EntityState.Deleted => CreateDeletedMessage(entry), EntityState.Modified => CreateModifiedMessage(entry), EntityState.Added => CreateAddedMessage(entry), _ => null }; if (auditMessage != null) { var alterIdStr = entry.CurrentValues["Id"].ToString(); audit.Entities.Add(new EntityAudit { State = entry.State, AuditMessage = auditMessage, AlterId = Guid.Parse(alterIdStr) }); } } return audit; string CreateAddedMessage(EntityEntry entry) => entry.Properties.Aggregate( $"Inserting {entry.Metadata.DisplayName()} with ", (auditString, property) => auditString + $"{property.Metadata.Name}: '{property.CurrentValue}' "); string CreateModifiedMessage(EntityEntry entry) => entry.Properties.Where(property => property.IsModified || property.Metadata.IsPrimaryKey()).Aggregate( $"Updating {entry.Metadata.DisplayName()} with ", (auditString, property) => auditString + $"{property.Metadata.Name}: '{property.CurrentValue}' "); string CreateDeletedMessage(EntityEntry entry) => entry.Properties.Where(property => property.Metadata.IsPrimaryKey()).Aggregate( $"Deleting {entry.Metadata.DisplayName()} with ", (auditString, property) => auditString + $"{property.Metadata.Name}: '{property.CurrentValue}' "); } #endregion }