161 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			C#
		
	
	
			
		
		
	
	
			161 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			C#
		
	
	
| 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<InterceptionResult<int>> SavingChangesAsync(
 | |
|         DbContextEventData eventData,
 | |
|         InterceptionResult<int> result,
 | |
|         CancellationToken cancellationToken = default)
 | |
|     {
 | |
|         _audit = CreateAudit(eventData.Context);
 | |
| 
 | |
|         await Task.CompletedTask;
 | |
|         return result;
 | |
| 
 | |
| 
 | |
|     }
 | |
| 
 | |
|     public InterceptionResult<int> SavingChanges(
 | |
|         DbContextEventData eventData,
 | |
|         InterceptionResult<int> 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<int> 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
 | |
| 
 | |
| } |