diff --git a/TrackerEnabledDbContext.Common/Configuration/GlobalTrackingConfig.cs b/TrackerEnabledDbContext.Common/Configuration/GlobalTrackingConfig.cs index 57e4d0b..a6ca6fd 100644 --- a/TrackerEnabledDbContext.Common/Configuration/GlobalTrackingConfig.cs +++ b/TrackerEnabledDbContext.Common/Configuration/GlobalTrackingConfig.cs @@ -6,7 +6,20 @@ namespace TrackerEnabledDbContext.Common.Configuration { public static class GlobalTrackingConfig { - public static bool Enabled { get; set; } = true; + public static bool Enabled + { + get { return AdditionsEnabled || ModificationsEnabled || DeletionsEnabled; } + set + { + AdditionsEnabled = value; + ModificationsEnabled = value; + DeletionsEnabled = value; + } + } + + public static bool AdditionsEnabled { get; set; } = true; + public static bool ModificationsEnabled { get; set; } = true; + public static bool DeletionsEnabled { get; set; } = true; public static bool TrackEmptyPropertiesOnAdditionAndDeletion { get; set; } = false; diff --git a/TrackerEnabledDbContext.Common/CoreTracker.cs b/TrackerEnabledDbContext.Common/CoreTracker.cs index 674ae15..00536e5 100644 --- a/TrackerEnabledDbContext.Common/CoreTracker.cs +++ b/TrackerEnabledDbContext.Common/CoreTracker.cs @@ -35,6 +35,63 @@ DbEntityEntry ent in { var eventType = GetEventType(ent); + AuditLog record = auditer.CreateLogRecord(userName, eventType, _context, metadata); + + if (record != null) + { + var arg = new AuditLogGeneratedEventArgs(record, ent.Entity, metadata); + RaiseOnAuditLogGenerated(this, arg); + if (!arg.SkipSavingLog) + { + _context.AuditLog.Add(record); + } + } + } + } + } + + public void AuditModifications(object userName, ExpandoObject metadata) + { + // Get all Modified entities (not Unmodified or Deleted or Detached or Added) + foreach ( + DbEntityEntry ent in + _context.ChangeTracker.Entries() + .Where(p => p.State == EntityState.Modified)) + { + using (var auditer = new LogAuditor(ent)) + { + AuditLog record = auditer.CreateLogRecord(userName, EventType.Modified, _context, metadata); + + if (record != null) + { + var arg = new AuditLogGeneratedEventArgs(record, ent.Entity, metadata); + RaiseOnAuditLogGenerated(this, arg); + if (!arg.SkipSavingLog) + { + _context.AuditLog.Add(record); + } + } + } + } + } + + public void AuditDeletions(object userName, ExpandoObject metadata) + { + // Get all Deleted or Modified entities (not Unmodified or Detached or Added) + foreach ( + DbEntityEntry ent in + _context.ChangeTracker.Entries() + .Where(p => p.State == EntityState.Deleted || p.State == EntityState.Modified)) + { + using (var auditer = new LogAuditor(ent)) + { + var eventType = GetEventType(ent); + + // Skip modifications, as these are handled in the AuditModifications method + if (eventType == EventType.Modified) + { + continue; + } AuditLog record = auditer.CreateLogRecord(userName, eventType, _context, metadata); if (record != null) diff --git a/TrackerEnabledDbContext.Common/Interfaces/ITrackerContext.cs b/TrackerEnabledDbContext.Common/Interfaces/ITrackerContext.cs index 2701124..0c78e81 100644 --- a/TrackerEnabledDbContext.Common/Interfaces/ITrackerContext.cs +++ b/TrackerEnabledDbContext.Common/Interfaces/ITrackerContext.cs @@ -13,6 +13,9 @@ public interface ITrackerContext : IDbContext DbSet AuditLog { get; set; } DbSet LogDetails { get; set; } bool TrackingEnabled { get; set; } + bool AdditionTrackingEnabled { get; set; } + bool ModificationTrackingEnabled { get; set; } + bool DeletionTrackingEnabled { get; set; } event EventHandler OnAuditLogGenerated; diff --git a/TrackerEnabledDbContext.Identity/TrackerIdentityContext.cs b/TrackerEnabledDbContext.Identity/TrackerIdentityContext.cs index 53a698b..e6f1862 100644 --- a/TrackerEnabledDbContext.Identity/TrackerIdentityContext.cs +++ b/TrackerEnabledDbContext.Identity/TrackerIdentityContext.cs @@ -34,16 +34,59 @@ public class TrackerIdentityContext _metadataConfiguration; - private bool _trackingEnabled = true; + private bool _additionTrackingEnabled = true; + private bool _modificationTrackingEnabled = true; + private bool _deletionTrackingEnabled = true; + public bool TrackingEnabled { get { - return GlobalTrackingConfig.Enabled && _trackingEnabled; + return GlobalTrackingConfig.Enabled && (_additionTrackingEnabled || + _modificationTrackingEnabled || + _deletionTrackingEnabled); + } + set + { + AdditionTrackingEnabled = value; + ModificationTrackingEnabled = value; + DeletionTrackingEnabled = value; + } + } + + public bool AdditionTrackingEnabled + { + get + { + return GlobalTrackingConfig.AdditionsEnabled && _additionTrackingEnabled; + } + set + { + _additionTrackingEnabled = value; + } + } + + public bool ModificationTrackingEnabled + { + get + { + return GlobalTrackingConfig.ModificationsEnabled && _modificationTrackingEnabled; } set { - _trackingEnabled = value; + _modificationTrackingEnabled = value; + } + } + + public bool DeletionTrackingEnabled + { + get + { + return GlobalTrackingConfig.DeletionsEnabled && _deletionTrackingEnabled; + } + set + { + _deletionTrackingEnabled = value; } } @@ -122,17 +165,28 @@ public virtual int SaveChanges(object userName) dynamic metadata = new ExpandoObject(); _metadataConfiguration?.Invoke(metadata); - _coreTracker.AuditChanges(userName, metadata); + if (ModificationTrackingEnabled) _coreTracker.AuditModifications(userName, metadata); + if (DeletionTrackingEnabled) _coreTracker.AuditDeletions(userName, metadata); - IEnumerable addedEntries = _coreTracker.GetAdditions(); - // Call the original SaveChanges(), which will save both the changes made and the audit records...Note that added entry auditing is still remaining. - int result = base.SaveChanges(); - //By now., we have got the primary keys of added entries of added entiries because of the call to savechanges. + int result; + if (AdditionTrackingEnabled) + { + IEnumerable addedEntries = _coreTracker.GetAdditions(); + // Call the original SaveChanges(), which will save both the changes made and the audit records...Note that added entry auditing is still remaining. + result = base.SaveChanges(); + //By now., we have got the primary keys of added entries of added entiries because of the call to savechanges. + + _coreTracker.AuditAdditions(userName, addedEntries, metadata); - _coreTracker.AuditAdditions(userName, addedEntries, metadata); + //save changes to audit of added entries + base.SaveChanges(); + } + else + { + //save changes + result = base.SaveChanges(); + } - //save changes to audit of added entries - base.SaveChanges(); return result; } @@ -219,18 +273,28 @@ public virtual async Task SaveChangesAsync(object userName, CancellationTok dynamic metadata = new ExpandoObject(); _metadataConfiguration?.Invoke(metadata); - _coreTracker.AuditChanges(userName, metadata); + if (ModificationTrackingEnabled) _coreTracker.AuditModifications(userName, metadata); + if (DeletionTrackingEnabled) _coreTracker.AuditDeletions(userName, metadata); - IEnumerable addedEntries = _coreTracker.GetAdditions(); + int result; + if (AdditionTrackingEnabled) + { + IEnumerable addedEntries = _coreTracker.GetAdditions(); - // Call the original SaveChanges(), which will save both the changes made and the audit records...Note that added entry auditing is still remaining. - int result = await base.SaveChangesAsync(cancellationToken); + // Call the original SaveChanges(), which will save both the changes made and the audit records...Note that added entry auditing is still remaining. + result = await base.SaveChangesAsync(cancellationToken); - //By now., we have got the primary keys of added entries of added entiries because of the call to savechanges. - _coreTracker.AuditAdditions(userName, addedEntries, metadata); + //By now., we have got the primary keys of added entries of added entiries because of the call to savechanges. + _coreTracker.AuditAdditions(userName, addedEntries, metadata); - //save changes to audit of added entries - await base.SaveChangesAsync(cancellationToken); + //save changes to audit of added entries + await base.SaveChangesAsync(cancellationToken); + } + else + { + //save changes + result = await base.SaveChangesAsync(cancellationToken); + } return result; } diff --git a/TrackerEnabledDbContext.IntegrationTests/FluentConfigurationTests.cs b/TrackerEnabledDbContext.IntegrationTests/FluentConfigurationTests.cs index 63843ee..6d890bc 100644 --- a/TrackerEnabledDbContext.IntegrationTests/FluentConfigurationTests.cs +++ b/TrackerEnabledDbContext.IntegrationTests/FluentConfigurationTests.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using TrackerEnabledDbContext.Common.Configuration; +using TrackerEnabledDbContext.Common.Models; using TrackerEnabledDbContext.Common.Testing; using TrackerEnabledDbContext.Common.Testing.Extensions; using TrackerEnabledDbContext.Common.Testing.Models; @@ -12,9 +14,9 @@ namespace TrackerEnabledDbContext.IntegrationTests public class FluentConfigurationTests : PersistanceTests { [TestMethod] - public void Can_recognise_global_tracking_indicator_when_disabled() + public void Can_recognise_global_addition_tracking_indicator_when_disabled() { - GlobalTrackingConfig.Enabled = false; + GlobalTrackingConfig.AdditionsEnabled = false; EntityTracker .TrackAllProperties() @@ -29,7 +31,7 @@ public void Can_recognise_global_tracking_indicator_when_disabled() } [TestMethod] - public void Can_recognise_global_tracking_indicator_when_enabled() + public void Can_recognise_global_addition_tracking_indicator_when_enabled() { EntityTracker .TrackAllProperties(); @@ -51,6 +53,96 @@ public void Can_recognise_global_tracking_indicator_when_enabled() x=>x.StartTime); } + [TestMethod] + public void Can_recognise_global_change_tracking_indicator_when_disabled() + { + GlobalTrackingConfig.ModificationsEnabled = false; + + EntityTracker + .TrackAllProperties() + .Except(x => x.StartTime) + .And(x => x.Color); + + POCO model = ObjectFactory.Create(); + Db.POCOs.Add(model); + Db.SaveChanges(); + model.Height++; + Db.SaveChanges(); + + model.AssertNoLogs(Db, model.Id, Common.Models.EventType.Modified); + } + + [TestMethod] + public void Can_recognise_global_change_tracking_indicator_when_enabled() + { + EntityTracker + .TrackAllProperties(); + + POCO model = new POCO + { + Color = "Red", + Height = 67.4, + StartTime = new DateTime(2015, 5, 5) + }; + + Db.POCOs.Add(model); + Db.SaveChanges(); + model.Color = "Green"; + Db.SaveChanges(); + + model.AssertAuditForModification(Db, model.Id, null, new AuditLogDetail + { + NewValue = "Green", + OriginalValue = "Red", + PropertyName = "Color" + }); + } + + [TestMethod] + public void Can_recognise_global_deletion_tracking_indicator_when_disabled() + { + GlobalTrackingConfig.DeletionsEnabled = false; + + EntityTracker + .TrackAllProperties() + .Except(x => x.StartTime) + .And(x => x.Color); + + POCO model = ObjectFactory.Create(); + Db.POCOs.Add(model); + Db.SaveChanges(); + Db.POCOs.Remove(model); + Db.SaveChanges(); + + model.AssertNoLogs(Db, model.Id, Common.Models.EventType.Modified); + } + + [TestMethod] + public void Can_recognise_global_deletion_tracking_indicator_when_enabled() + { + EntityTracker + .TrackAllProperties(); + + POCO model = new POCO + { + Color = "Red", + Height = 67.4, + StartTime = new DateTime(2015, 5, 5) + }; + + Db.POCOs.Add(model); + Db.SaveChanges(); + Db.POCOs.Remove(model); + Db.ChangeTracker.DetectChanges(); + Db.SaveChanges(); + + model.AssertAuditForDeletion(Db, model.Id, null, + x => x.Id, + x => x.Color, + x => x.Height, + x => x.StartTime); + } + [TestMethod] public async Task Can_Override_annotation_based_configuration_for_entity_skipTracking() { diff --git a/TrackerEnabledDbContext/TrackerContext.cs b/TrackerEnabledDbContext/TrackerContext.cs index 233b750..feb9e1f 100644 --- a/TrackerEnabledDbContext/TrackerContext.cs +++ b/TrackerEnabledDbContext/TrackerContext.cs @@ -27,18 +27,59 @@ public class TrackerContext : DbContext, ITrackerContext private string _defaultUsername; private Action _metadataConfiguration; - - private bool _trackingEnabled = true; + private bool _additionTrackingEnabled = true; + private bool _modificationTrackingEnabled = true; + private bool _deletionTrackingEnabled = true; public bool TrackingEnabled { get { - return GlobalTrackingConfig.Enabled && _trackingEnabled; + return GlobalTrackingConfig.Enabled && (_additionTrackingEnabled || + _modificationTrackingEnabled || + _deletionTrackingEnabled); + } + set + { + _additionTrackingEnabled = value; + _modificationTrackingEnabled = value; + _deletionTrackingEnabled = value; + } + } + + public bool AdditionTrackingEnabled + { + get + { + return GlobalTrackingConfig.AdditionsEnabled && _additionTrackingEnabled; + } + set + { + _additionTrackingEnabled = value; + } + } + + public bool ModificationTrackingEnabled + { + get + { + return GlobalTrackingConfig.ModificationsEnabled && _modificationTrackingEnabled; } set { - _trackingEnabled = value; + _modificationTrackingEnabled = value; + } + } + + public bool DeletionTrackingEnabled + { + get + { + return GlobalTrackingConfig.DeletionsEnabled && _deletionTrackingEnabled; + } + set + { + _deletionTrackingEnabled = value; } } @@ -122,17 +163,27 @@ public virtual int SaveChanges(object userName) dynamic metaData = new ExpandoObject(); _metadataConfiguration?.Invoke(metaData); - _coreTracker.AuditChanges(userName, metaData); - - IEnumerable addedEntries = _coreTracker.GetAdditions(); - // Call the original SaveChanges(), which will save both the changes made and the audit records...Note that added entry auditing is still remaining. - int result = base.SaveChanges(); - //By now., we have got the primary keys of added entries of added entiries because of the call to savechanges. + if (ModificationTrackingEnabled) _coreTracker.AuditModifications(userName, metaData); + if (DeletionTrackingEnabled) _coreTracker.AuditDeletions(userName, metaData); - _coreTracker.AuditAdditions(userName, addedEntries, metaData); - - //save changes to audit of added entries - base.SaveChanges(); + int result; + if (AdditionTrackingEnabled) + { + IEnumerable addedEntries = _coreTracker.GetAdditions(); + // Call the original SaveChanges(), which will save both the changes made and the audit records...Note that added entry auditing is still remaining. + result = base.SaveChanges(); + //By now., we have got the primary keys of added entries of added entiries because of the call to savechanges. + + _coreTracker.AuditAdditions(userName, addedEntries, metaData); + //save changes to audit of added entries + base.SaveChanges(); + } + else + { + //save changes + result = base.SaveChanges(); + } + return result; } @@ -212,18 +263,28 @@ public virtual async Task SaveChangesAsync(object userName, CancellationTok dynamic metadata = new ExpandoObject(); _metadataConfiguration?.Invoke(metadata); - _coreTracker.AuditChanges(userName, metadata); + if (ModificationTrackingEnabled) _coreTracker.AuditModifications(userName, metadata); + if (DeletionTrackingEnabled) _coreTracker.AuditDeletions(userName, metadata); - IEnumerable addedEntries = _coreTracker.GetAdditions(); + int result; + if (AdditionTrackingEnabled) + { + IEnumerable addedEntries = _coreTracker.GetAdditions(); - // Call the original SaveChanges(), which will save both the changes made and the audit records...Note that added entry auditing is still remaining. - int result = await base.SaveChangesAsync(cancellationToken); + // Call the original SaveChanges(), which will save both the changes made and the audit records...Note that added entry auditing is still remaining. + result = await base.SaveChangesAsync(cancellationToken); - //By now., we have got the primary keys of added entries of added entiries because of the call to savechanges. - _coreTracker.AuditAdditions(userName, addedEntries, metadata); + //By now., we have got the primary keys of added entries of added entiries because of the call to savechanges. + _coreTracker.AuditAdditions(userName, addedEntries, metadata); - //save changes to audit of added entries - await base.SaveChangesAsync(cancellationToken); + //save changes to audit of added entries + await base.SaveChangesAsync(cancellationToken); + } + else + { + //save changes + result = await base.SaveChangesAsync(cancellationToken); + } return result; }