Maarten1977

Entity Framework Audit Changes Detector

Jan 22nd, 2015
253
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 8.31 KB | None | 0 0
  1. public interface IAuditChangesDetector {
  2.     AuditInfo[] Detect();
  3.     AuditInfo[] PostProcess(AuditInfo[] info);
  4. }
  5.    
  6. public class AuditInfo {
  7.     public Guid AuditSetId { get; set; }
  8.     public DateTime AuditDateTime { get; set; }
  9.     public string ExecutedByUser { get; set; }
  10.     public string ActionType { get; set; }
  11.     public string Entity { get; set; }
  12.     public string EntityKey { get; set; }
  13.     public AuditPropertyInfo[] Properties { get; set; }
  14. }
  15.  
  16. public class AuditPropertyInfo {
  17.     public string Property { get; set; }
  18.     public string OldValue { get; set; }
  19.     public string NewValue { get; set; }
  20. }
  21.  
  22. public class EfAuditChangesDetector : IAuditChangesDetector {
  23.     public EfAuditChangesDetector(DbContext context) {
  24.         if (context == null)
  25.             throw new ArgumentNullException("context");
  26.  
  27.         _dbContext = context;
  28.     }
  29.  
  30.     private readonly DbContext _dbContext;
  31.  
  32.     private ObjectStateManager _objectStateManager;
  33.  
  34.     AuditInfo[] IAuditChangesDetector.Detect() {
  35.         if (!(_dbContext is IObjectContextAdapter))
  36.             throw new InvalidOperationException("The context should be an IObjectContextAdapter).");
  37.         var adapter = _dbContext as IObjectContextAdapter;
  38.         if (adapter == null || adapter.ObjectContext == null || adapter.ObjectContext.ObjectStateManager == null)
  39.             throw new InvalidOperationException("The object-state-manager could not be found.");
  40.         _objectStateManager = adapter.ObjectContext.ObjectStateManager;
  41.  
  42.         var info = new List<AuditInfo>();
  43.         var auditSetId = Guid.NewGuid();
  44.         var auditDateTime = DateTime.Now;
  45.         var executedByUser = "<username>";
  46.  
  47.         Func<ActionType, EfAuditInfo> createInfo = a => new EfAuditInfo {
  48.             AuditSetId = auditSetId,
  49.             AuditDateTime = auditDateTime,
  50.             ExecutedByUser = executedByUser,
  51.             ActionType = a.ToString(),
  52.             ActionTypeEx = a
  53.         };
  54.  
  55.         var deletedObjects = FindDeletedObjects(createInfo);
  56.         var modifiedObjects = FindModifiedObjects(createInfo);
  57.         var addedObjects = FindAddedObjects(createInfo);
  58.  
  59.         return deletedObjects
  60.             .Concat(modifiedObjects)
  61.             .Concat(addedObjects)
  62.             .ToArray();
  63.     }
  64.  
  65.     private EfAuditInfo[] FindDeletedObjects(Func<ActionType, EfAuditInfo> createInfo) {
  66.         var deletedObjects = _objectStateManager.GetObjectStateEntries(EntityState.Deleted).ToArray();
  67.         var infos = new List<EfAuditInfo>();
  68.         foreach (var deletedObject in deletedObjects) {
  69.             var pks = deletedObject.EntityKey.EntityKeyValues.Select(x => x.Key).ToArray();
  70.             var pkValues = new List<string>();
  71.             var entry = _dbContext.Entry(deletedObject.Entity);
  72.  
  73.             var audit = createInfo(ActionType.Deleted);
  74.             audit.Entity = deletedObject.EntitySet.Name;
  75.  
  76.             var auditProperties = new List<AuditPropertyInfo>();
  77.  
  78.             foreach (var propertyName in entry.OriginalValues.PropertyNames) {
  79.                 var oldValue = entry.Property(propertyName).OriginalValue;
  80.                 var oldValueString = oldValue != null ? oldValue.ToString() : null;
  81.  
  82.                 var isPk = pks.Contains(propertyName);
  83.                 if (isPk) {
  84.                     pkValues.Add(oldValueString);
  85.                 }
  86.  
  87.                 if (propertyName.ToLower() != "timestamp" && oldValueString != "System.Byte[]") {
  88.                     var auditPropertyInfo = new AuditPropertyInfo {
  89.                         Property = propertyName,
  90.                         OldValue = oldValueString,
  91.                         NewValue = oldValueString
  92.                     };
  93.                     auditProperties.Add(auditPropertyInfo);
  94.                 }
  95.             }
  96.  
  97.             audit.Properties = auditProperties.ToArray();
  98.             audit.EntityKey = string.Join(";", pkValues);
  99.             audit.ObjectStateEntry = deletedObject;
  100.             audit.DbEntityEntry = entry;
  101.  
  102.             infos.Add(audit);
  103.         }
  104.  
  105.         return infos.ToArray();
  106.     }
  107.  
  108.     private EfAuditInfo[] FindModifiedObjects(Func<ActionType, EfAuditInfo> createInfo) {
  109.         var modifiedObjects = _objectStateManager.GetObjectStateEntries(EntityState.Modified).ToList();
  110.         var infos = new List<EfAuditInfo>();
  111.         foreach (var modifiedObject in modifiedObjects) {
  112.             var pks = modifiedObject.EntityKey.EntityKeyValues.Select(x => x.Key).ToList();
  113.             var pkValues = new List<string>();
  114.             var entry = _dbContext.Entry(modifiedObject.Entity);
  115.  
  116.             var audit = createInfo(ActionType.Modified);
  117.             audit.Entity = modifiedObject.EntitySet.Name;
  118.  
  119.             var auditProperties = new List<AuditPropertyInfo>();
  120.  
  121.             foreach (var propertyName in entry.CurrentValues.PropertyNames) {
  122.                 var oldValue = entry.Property(propertyName).OriginalValue;
  123.                 var oldValueString = oldValue != null ? oldValue.ToString() : null;
  124.                 var currentValue = entry.Property(propertyName).CurrentValue;
  125.                 var currentValueString = currentValue != null ? currentValue.ToString() : null;
  126.  
  127.                 var isPk = pks.Contains(propertyName);
  128.                 if (isPk) {
  129.                     pkValues.Add(currentValueString);
  130.                 }
  131.  
  132.                 if (propertyName.ToLower() != "timestamp" && oldValueString != "System.Byte[]") {
  133.                     // filter out the timestamp column
  134.                     if (string.CompareOrdinal(oldValueString, currentValueString) != 0 || isPk) {
  135.                         var auditPropertyInfo = new AuditPropertyInfo {
  136.                             Property = propertyName,
  137.                             OldValue = oldValueString,
  138.                             NewValue = currentValueString
  139.                         };
  140.                         auditProperties.Add(auditPropertyInfo);
  141.                     }
  142.                 }
  143.             }
  144.  
  145.             audit.Properties = auditProperties.ToArray();
  146.             audit.EntityKey = string.Join(";", pkValues);
  147.             audit.ObjectStateEntry = modifiedObject;
  148.             audit.DbEntityEntry = entry;
  149.  
  150.             infos.Add(audit);
  151.         }
  152.  
  153.         return infos.ToArray();
  154.     }
  155.  
  156.     private EfAuditInfo[] FindAddedObjects(Func<ActionType, EfAuditInfo> createInfo) {
  157.         var addedObjects = _objectStateManager.GetObjectStateEntries(EntityState.Added).ToList();
  158.         var infos = new List<EfAuditInfo>();
  159.         foreach (var addedObject in addedObjects) {
  160.             var entry = _dbContext.Entry(addedObject.Entity);
  161.  
  162.             var audit = createInfo(ActionType.Added);
  163.             audit.Entity = addedObject.EntitySet.Name;
  164.  
  165.             var auditProperties = new List<AuditPropertyInfo>();
  166.  
  167.             foreach (var propertyName in entry.CurrentValues.PropertyNames) {
  168.                 var currentValue = entry.Property(propertyName).CurrentValue;
  169.                 var currentValueString = currentValue != null ? currentValue.ToString() : null;
  170.                 if (propertyName.ToLower() != "timestamp" && currentValueString != "System.Byte[]") {
  171.                     var auditPropertyInfo = new AuditPropertyInfo {
  172.                         Property = propertyName,
  173.                         NewValue = currentValueString
  174.                     };
  175.                     auditProperties.Add(auditPropertyInfo);
  176.                 }
  177.             }
  178.  
  179.             audit.Properties = auditProperties.ToArray();
  180.             audit.ObjectStateEntry = addedObject;
  181.             audit.DbEntityEntry = entry;
  182.  
  183.             infos.Add(audit);
  184.         }
  185.  
  186.         return infos.ToArray();
  187.     }
  188.  
  189.  
  190.     AuditInfo[] IAuditChangesDetector.PostProcess(AuditInfo[] auditInfo) {
  191.         var infos = auditInfo.Cast<EfAuditInfo>()
  192.             .Where(x => x.DbEntityEntry.State != EntityState.Detached)
  193.             .Where(x => x.ActionTypeEx == ActionType.Added || x.ActionTypeEx == ActionType.Modified)
  194.             .ToArray();
  195.  
  196.         foreach (var info in infos){
  197.             var pks = info.ObjectStateEntry.EntityKey.EntityKeyValues.Select(x => x.Key).ToArray();
  198.             var pkValues = new List<string>();
  199.             foreach (var propertyInfo in info.Properties) {
  200.                 // Get new current value for changed properties. Basically this updates the id's with actual values.
  201.                 var propertyName = propertyInfo.Property;
  202.                 var currentValue =  info.DbEntityEntry.Property(propertyName).CurrentValue;
  203.                 var currentValueString = currentValue != null ? currentValue.ToString() : null;
  204.                 if (info.ActionTypeEx == ActionType.Added || info.ActionTypeEx == ActionType.Modified) {
  205.                     propertyInfo.NewValue = currentValueString;
  206.                 }
  207.                 // Generate single PK value
  208.                 var isPk = pks.Contains(propertyName);
  209.                 if (isPk) {
  210.                     pkValues.Add(currentValueString);
  211.                 }
  212.             }
  213.             info.EntityKey = string.Join(";", pkValues);
  214.         }
  215.  
  216.         return auditInfo;
  217.     }
  218.  
  219.     private enum ActionType {
  220.         Added,
  221.         Modified,
  222.         Deleted
  223.     }
  224.  
  225.     private class EfAuditInfo : AuditInfo {
  226.         public DbEntityEntry DbEntityEntry { get; set; }
  227.         public ObjectStateEntry ObjectStateEntry { get; set; }
  228.         public ActionType ActionTypeEx { get; set; }
  229.     }
  230. }
  231.  
  232. // Usage:
  233. using (var context = someDbContextFactory.Create()) {
  234.     DoLotsOfWorkWithTheContext(context);
  235.    
  236.     // Create detector 
  237.     IAuditChangesDetector detector = new EfAuditChangesDetector(context);
  238.  
  239.     // Retrieve audit info
  240.     var auditInfo = detector.Detect();
  241.    
  242.     // Save changes
  243.     context.SaveChanges();
  244.  
  245.     // Post process audit info to set the inserted id's
  246.     auditInfo = detector.PostProcess(auditInfo);
  247.  
  248.     // Now you can save the audit info somewhere, in a logfile, a new context, whatever...
  249. }
Advertisement
Add Comment
Please, Sign In to add comment