using System.Text.Json; using System.Text.Json.Serialization; using Manager.Data.Entities.Audit; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; namespace Manager.Data.Contexts; public class AuditInterceptor : SaveChangesInterceptor { public override InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result) { AddAudit(eventData.Context); return base.SavingChanges(eventData, result); } public override ValueTask> SavingChangesAsync( DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) { AddAudit(eventData.Context); return base.SavingChangesAsync(eventData, result, cancellationToken); } private void AddAudit(DbContext? context) { if (context == null) return; var entries = context.ChangeTracker.Entries() .Where(e => e.State is EntityState.Modified or EntityState.Deleted or EntityState.Added && Attribute.IsDefined(e.Entity.GetType(), typeof(AuditableAttribute))); var audits = new List(); foreach (var entry in entries) { var primaryKey = entry.Properties.First(p => p.Metadata.IsPrimaryKey()).CurrentValue?.ToString(); var declaredProperties = entry.Entity.GetType() .GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance) .Where(p => !Attribute.IsDefined(p.DeclaringType!, typeof(NoAuditAttribute), false)) .Select(p => p.Name) .ToHashSet(); var allowedProperties = entry.Properties.Where(p => declaredProperties.Contains(p.Metadata.Name)); switch (entry.State) { case EntityState.Added: audits.AddRange(allowedProperties .Where(p => p.CurrentValue != null) .Select(p => CreateAudit(entry, p, entry.State, primaryKey)) ); break; case EntityState.Modified: audits.AddRange(allowedProperties .Where(p => p.IsModified) .Select(p => CreateAudit(entry, p, entry.State, primaryKey)) ); break; case EntityState.Deleted: audits.AddRange(allowedProperties .Select(p => CreateAudit(entry, p, entry.State, primaryKey)) ); break; } } if (audits.Count != 0) { context.Set().AddRange(audits); } } private EntityAudit CreateAudit(EntityEntry entry, PropertyEntry prop, EntityState changeType, string? primaryKey) { return new EntityAudit { Id = Guid.NewGuid(), EntityName = entry.Entity.GetType().Name, EntityId = primaryKey ?? "Unknown", PropertyName = prop.Metadata.Name, OldValue = SerializeValue(prop.OriginalValue), NewValue = SerializeValue(prop.CurrentValue), ModifiedUtc = DateTime.UtcNow, ChangedBy = "SYSTEM", ChangeType = changeType }; } private readonly JsonSerializerOptions _jsonSerializerOptions = new() { WriteIndented = false, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; private string? SerializeValue(object? value) { if (value == null) return null; var type = value.GetType(); if (type.IsPrimitive || type == typeof(string) || type == typeof(DateTime) || type == typeof(decimal)) { return value.ToString(); } return JsonSerializer.Serialize(value, _jsonSerializerOptions); } }