111 lines
4.0 KiB
C#
111 lines
4.0 KiB
C#
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<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
|
|
{
|
|
AddHistory(eventData.Context);
|
|
return base.SavingChanges(eventData, result);
|
|
}
|
|
|
|
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
|
|
DbContextEventData eventData,
|
|
InterceptionResult<int> result,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
AddHistory(eventData.Context);
|
|
return base.SavingChangesAsync(eventData, result, cancellationToken);
|
|
}
|
|
|
|
private void AddHistory(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 histories = new List<EntityHistory>();
|
|
|
|
foreach (var entry in entries)
|
|
{
|
|
var primaryKey = entry.Properties.First(p => p.Metadata.IsPrimaryKey()).CurrentValue?.ToString() ?? "Unknown";
|
|
|
|
var declaredProperties = entry.Entity.GetType()
|
|
.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
|
|
.Where(p => !Attribute.IsDefined(p.DeclaringType!, typeof(NoAuditAttribute)))
|
|
.Select(p => p.Name)
|
|
.ToHashSet();
|
|
|
|
var allowedProperties = entry.Properties.Where(p => declaredProperties.Contains(p.Metadata.Name));
|
|
|
|
switch (entry.State)
|
|
{
|
|
case EntityState.Added:
|
|
histories.AddRange(allowedProperties
|
|
.Where(p => p.CurrentValue != null)
|
|
.Select(p => CreateHistory(entry, p, entry.State, primaryKey))
|
|
);
|
|
break;
|
|
case EntityState.Modified:
|
|
histories.AddRange(allowedProperties
|
|
.Where(p => p.IsModified)
|
|
.Select(p => CreateHistory(entry, p, entry.State, primaryKey))
|
|
);
|
|
break;
|
|
case EntityState.Deleted:
|
|
histories.AddRange(allowedProperties
|
|
.Select(p => CreateHistory(entry, p, entry.State, primaryKey))
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (histories.Count != 0)
|
|
{
|
|
context.Set<EntityHistory>().AddRange(histories);
|
|
}
|
|
}
|
|
|
|
private EntityHistory CreateHistory(EntityEntry entry, PropertyEntry prop, EntityState changeType, string? primaryKey)
|
|
{
|
|
return new EntityHistory
|
|
{
|
|
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);
|
|
}
|
|
} |