public override int SaveChanges() { var username = WebUser.Current?.Username ?? "(unknown)"; new AuditListener(this, ChangeTracker).LogChanges(username); return base.SaveChanges(); }
AuditListener
using System.Collections.Generic; using System.Data.Entity.Infrastructure; using System.Data.Entity; using System.Text.Json; using DentaCAD.DAL.Queues; // USAGE // Override your DbContext.SaveChanges method, and add the following code before // before the "return base.SaveChanges();" line /* var username = WebUser.Current.Username; // TODO: Change this as appropriate new AuditListener(this, ChangeTracker).LogChanges(username); */ // class AuditListener { private DbContext _dbContext; private DbChangeTracker _changeTracker; public AuditListener(DbContext dbContext, DbChangeTracker changeTracker) { _dbContext = dbContext; _changeTracker = changeTracker; } public void LogChanges(string byUsername) { var txn = new AuditTransaction { ByUsername = WebUser.Current.Username }; var persist = false; /* 06/15/20 - Removed EntityState.Added, because for these entities we have no values for the PK field(s). */ /* It doesn't matter though, because we'll either have them in the database table (if the row is unchanged), */ /* or in a subsequent audit record. */ var watchFor = new List<EntityState>() { /*EntityState.Added,*/ EntityState.Modified, EntityState.Deleted }; var ignoreEntities = new List<string>() { "SystemLog" }; foreach (var entry in _changeTracker.Entries()) { if (!watchFor.Contains(entry.State)) { continue; } persist = true; var ec = new EntityChange(_dbContext, entry); /* 06/15/20 - Added the ignoring of specific entities and those without a primary key. */ if (ignoreEntities.Contains(ec.Name) || ec.PrimaryKey.Length == 0) { continue; } txn.EntityChanges.Add(ec); foreach (var prop in entry.OriginalValues.PropertyNames) { var oldValue = $"{entry.OriginalValues[prop]}"; var newValue = $"{entry.CurrentValues[prop]}"; if (oldValue == newValue) { continue; } var pc = new PropertyChange { Name = prop, OldValue = oldValue, NewValue = newValue }; ec.PropertyChanges.Add(pc); } } if (!persist) { return; } QueueEngine.AddToAuditTrailQueue(txn); } }
AuditTransaction
public class AuditTransaction { public AuditTransaction() { EntityChanges = new List<EntityChange>(); OccurredOn = DateTime.Now; } public string ByUsername { get; set; } public DateTime OccurredOn { get; set; } public List<EntityChange> EntityChanges { get; set; } }
EntityChange
public class EntityChange { public EntityChange(DbContext dbContext, DbEntityEntry entry) { PropertyChanges = new List<PropertyChange>(); Name = entry.Entity.GetType().Name; Action = entry.State.ToString(); var objectStateEntry = ((IObjectContextAdapter)dbContext).ObjectContext .ObjectStateManager .GetObjectStateEntry(entry.Entity); var keys = objectStateEntry.EntityKey.EntityKeyValues; /* 06/15/20 - Added code to handle nulls. This is expected to happend only when entities are added */ if (keys == null) { PrimaryKey = string.Empty; } else if (keys.Count() == 1) { PrimaryKey = keys[0].Value.ToString(); } else { PrimaryKey = string.Join("|", keys.Select(key => $"{key.Key}={key.Value}").ToList()); } } public string Name { get; set; } public string PrimaryKey { get; set; } public string Action { get; set; } public List<PropertyChange> PropertyChanges { get; set; } }
PropertyChange
public class PropertyChange { public string Name { get; set; } public string OldValue { get; set; } public string NewValue { get; set; } }