diff --git a/Source/EntityFramework.Extended/Audit/AuditEntryState.cs b/Source/EntityFramework.Extended/Audit/AuditEntryState.cs index ccbc035..1c9034a 100644 --- a/Source/EntityFramework.Extended/Audit/AuditEntryState.cs +++ b/Source/EntityFramework.Extended/Audit/AuditEntryState.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Data; using System.Data.Metadata.Edm; using System.Data.Objects; +using EntityFramework.Extensions; using EntityFramework.Reflection; namespace EntityFramework.Audit @@ -10,31 +12,34 @@ internal class AuditEntryState { public AuditEntryState(ObjectStateEntry objectStateEntry) { - if (objectStateEntry == null) - throw new ArgumentNullException("objectStateEntry"); + if (objectStateEntry == null) + throw new ArgumentNullException("objectStateEntry"); - if (objectStateEntry.Entity == null) - throw new ArgumentException("The Entity property is null for the specified ObjectStateEntry.", "objectStateEntry"); + if (objectStateEntry.Entity == null) + throw new ArgumentException("The Entity property is null for the specified ObjectStateEntry.", "objectStateEntry"); - ObjectStateEntry = objectStateEntry; - Entity = objectStateEntry.Entity; + ObjectStateEntry = objectStateEntry; + Entity = objectStateEntry.Entity; - EntityType = objectStateEntry.EntitySet.ElementType as EntityType; + EntityType = Extensions.GetInheritedEntityType(objectStateEntry); - Type entityType = objectStateEntry.Entity.GetType(); - entityType = ObjectContext.GetObjectType(entityType); + Type entityType = objectStateEntry.Entity.GetType(); + entityType = ObjectContext.GetObjectType(entityType); - ObjectType = entityType; - EntityAccessor = TypeAccessor.GetAccessor(entityType); + ObjectType = entityType; + EntityAccessor = TypeAccessor.GetAccessor(entityType); - AuditEntity = new AuditEntity(objectStateEntry.Entity) - { - Action = GetAction(objectStateEntry), - }; + AuditEntity = new AuditEntity(objectStateEntry.Entity) + { + Action = GetAction(objectStateEntry), + }; } public ObjectContext ObjectContext { get; set; } public AuditLog AuditLog { get; set; } + public NavigationProperty AdditionalModifiedProperty { get; set; } + public object AdditionalModifiedPropertyEnd { get; set; } + public bool AdditionalModifiedPropertyIsAdd { get; set; } public object Entity { get; private set; } public Type ObjectType { get; private set; } diff --git a/Source/EntityFramework.Extended/Audit/AuditLogger.cs b/Source/EntityFramework.Extended/Audit/AuditLogger.cs index c0ea0c7..14b05f0 100644 --- a/Source/EntityFramework.Extended/Audit/AuditLogger.cs +++ b/Source/EntityFramework.Extended/Audit/AuditLogger.cs @@ -150,16 +150,73 @@ public AuditLog UpdateLog(AuditLog auditLog) // must call to make sure changes are detected ObjectContext.DetectChanges(); - IEnumerable changes = ObjectContext - .ObjectStateManager - .GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified); + IEnumerable changes = ObjectContext + .ObjectStateManager + .GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified) + .Where(t => !t.IsRelationship); + + + foreach (ObjectStateEntry objectStateEntry in ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted).Where(t => t.IsRelationship)) + { + ObjectStateEntry entryLeft = null; + ObjectStateEntry entryRight = null; + var associationEnds = objectStateEntry.GetAssociationEnds(); + NavigationProperty navigationPropertyLeft = objectStateEntry.GetNavigationProperty(associationEnds[0]); + NavigationProperty navigationPropertyRight = objectStateEntry.GetNavigationProperty(associationEnds[1]); + + if (objectStateEntry.State == EntityState.Added) + { + entryLeft = ObjectContext.ObjectStateManager.GetObjectStateEntry((EntityKey)objectStateEntry.CurrentValues[0]); + entryRight = ObjectContext.ObjectStateManager.GetObjectStateEntry((EntityKey)objectStateEntry.CurrentValues[1]); + } + else if (objectStateEntry.State == EntityState.Deleted) + { + entryLeft = ObjectContext.ObjectStateManager.GetObjectStateEntry((EntityKey)objectStateEntry.OriginalValues[0]); + entryRight = ObjectContext.ObjectStateManager.GetObjectStateEntry((EntityKey)objectStateEntry.OriginalValues[1]); + } + + Type entityTypeLeft = entryLeft.Entity.GetType(); + entityTypeLeft = ObjectContext.GetObjectType(entityTypeLeft); + if (Configuration.IsAuditable(entityTypeLeft)) + { + var stateLeft = new AuditEntryState(entryLeft) + { + AuditLog = auditLog, + ObjectContext = ObjectContext, + AdditionalModifiedProperty = navigationPropertyLeft, + AdditionalModifiedPropertyEnd = ObjectContext.GetObjectByKey(entryRight.EntityKey), + AdditionalModifiedPropertyIsAdd = objectStateEntry.State == EntityState.Added + }; + + if (WriteEntity(stateLeft)) + auditLog.Entities.Add(stateLeft.AuditEntity); + } + + Type entityTypeRight = entryRight.Entity.GetType(); + entityTypeRight = ObjectContext.GetObjectType(entityTypeRight); + if (Configuration.IsAuditable(entityTypeRight)) + { + var stateRight = new AuditEntryState(entryRight) + { + AuditLog = auditLog, + ObjectContext = ObjectContext, + AdditionalModifiedProperty = navigationPropertyRight, + AdditionalModifiedPropertyEnd = ObjectContext.GetObjectByKey(entryLeft.EntityKey), + AdditionalModifiedPropertyIsAdd = objectStateEntry.State == EntityState.Added + }; + + if (WriteEntity(stateRight)) + auditLog.Entities.Add(stateRight.AuditEntity); + } + } + + foreach (ObjectStateEntry objectStateEntry in changes) + { + if (objectStateEntry.Entity == null) + continue; + + Type entityType = objectStateEntry.Entity.GetType(); - foreach (ObjectStateEntry objectStateEntry in changes) - { - if (objectStateEntry.Entity == null) - continue; - - Type entityType = objectStateEntry.Entity.GetType(); entityType = ObjectContext.GetObjectType(entityType); if (!Configuration.IsAuditable(entityType)) continue; @@ -308,7 +365,7 @@ private void WriteRelationships(AuditEntryState state) if (!Configuration.IncludeRelationships) return; - var properties = state.EntityType.NavigationProperties; + var properties = state.EntityType.NavigationProperties.ToList(); if (properties.Count == 0) return; @@ -316,7 +373,10 @@ private void WriteRelationships(AuditEntryState state) .GetModifiedProperties() .ToList(); - var type = state.ObjectType; + if (state.AdditionalModifiedProperty != null) + modifiedMembers.Add(state.AdditionalModifiedProperty.Name); + + var type = state.ObjectType; var currentValues = state.IsDeleted ? state.ObjectStateEntry.OriginalValues @@ -328,10 +388,6 @@ private void WriteRelationships(AuditEntryState state) foreach (NavigationProperty navigationProperty in properties) { - if (navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many - || navigationProperty.FromEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many) - continue; - string name = navigationProperty.Name; if (Configuration.IsNotAudited(type, name)) continue; @@ -343,12 +399,16 @@ private void WriteRelationships(AuditEntryState state) bool isModified = IsModifed(navigationProperty, modifiedMembers); + if (navigationProperty == state.AdditionalModifiedProperty) isModified = true; + if (state.IsModified && !isModified && !Configuration.IsAlwaysAudited(type, name)) continue; // this means the property was not changed, skip it - bool isLoaded = IsLoaded(state, navigationProperty, accessor); - if (!isLoaded && !Configuration.LoadRelationships) + bool isLoaded = navigationProperty == state.AdditionalModifiedProperty || + IsLoaded(state, navigationProperty, accessor); + + if (!isLoaded && !Configuration.LoadRelationships) continue; var auditProperty = new AuditProperty(); @@ -362,7 +422,13 @@ private void WriteRelationships(AuditEntryState state) object currentValue; - if (isLoaded) + if (isLoaded && navigationProperty == state.AdditionalModifiedProperty) + { + currentValue = state.AdditionalModifiedPropertyEnd; + auditProperty.IsManyToMany = true; + auditProperty.IsManyToManyAdd = state.AdditionalModifiedPropertyIsAdd; + } + else if (isLoaded) { // get value directly from instance to save db call object valueInstance = accessor.GetValue(state.Entity); @@ -503,7 +569,7 @@ private static object GetDisplayValue(AuditEntryState state, NavigationProperty return null; var edmType = referentialConstraint - .FromProperties + .ToProperties .Select(p => p.DeclaringType) .FirstOrDefault(); @@ -568,13 +634,18 @@ private static bool IsLoaded(AuditEntryState state, NavigationProperty navigatio { var relationshipManager = state.ObjectStateEntry.RelationshipManager; var getEntityReference = _relatedAccessor.Value.MakeGenericMethod(accessor.MemberType); - var parameters = new[] + var parameters = new object[] { navigationProperty.RelationshipType.FullName, navigationProperty.ToEndMember.Name }; - var entityReference = getEntityReference.Invoke(relationshipManager, parameters) as EntityReference; + EntityReference entityReference = null; + try + { + entityReference = getEntityReference.Invoke(relationshipManager, parameters) as EntityReference; + } + catch(TargetInvocationException){} return (entityReference != null && entityReference.IsLoaded); } diff --git a/Source/EntityFramework.Extended/Audit/AuditProperty.cs b/Source/EntityFramework.Extended/Audit/AuditProperty.cs index bb5d3c7..d30e54f 100644 --- a/Source/EntityFramework.Extended/Audit/AuditProperty.cs +++ b/Source/EntityFramework.Extended/Audit/AuditProperty.cs @@ -56,5 +56,19 @@ public class AuditProperty /// The original value of the property. [XmlElement("original")] public object Original { get; set; } + + /// + /// Gets or sets the is many to many value of the property + /// + /// True if this represents a change to a many to many relationship + [XmlElement("isManyToMany")] + public bool IsManyToMany { get; set; } + + /// + /// Gets or sets the is many to many value of the property + /// + /// True if this many to many change represents an insert, false if a delete + [XmlElement("isManyToManyAdd")] + public bool IsManyToManyAdd { get; set; } } } \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Audit/Extensions.cs b/Source/EntityFramework.Extended/Audit/Extensions.cs new file mode 100644 index 0000000..c72b318 --- /dev/null +++ b/Source/EntityFramework.Extended/Audit/Extensions.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Metadata.Edm; +using System.Data.Objects; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EntityFramework.Audit +{ + public static class Extensions + { + public static IExtendedDataRecord UsableValues(this ObjectStateEntry entry) + { + switch (entry.State) + { + case EntityState.Added: + case EntityState.Detached: + case EntityState.Unchanged: + case EntityState.Modified: + return (IExtendedDataRecord)entry.CurrentValues; + case EntityState.Deleted: + return (IExtendedDataRecord)entry.OriginalValues; + default: + throw new InvalidOperationException("This entity state should not exist."); + } + } + + public static AssociationEndMember[] GetAssociationEnds(this ObjectStateEntry entry) + { + var fieldMetadata = + entry.UsableValues().DataRecordInfo.FieldMetadata; + + return fieldMetadata.Select( + m => m.FieldType as AssociationEndMember).ToArray(); + } + + public static AssociationEndMember GetOtherAssociationEnd( + this ObjectStateEntry entry, AssociationEndMember end) + { + end.ValidateBelongsTo(entry); + AssociationEndMember[] ends = entry.GetAssociationEnds(); + if (ends[0] == end) + { + return ends[1]; + } + return ends[0]; + } + + public static EntityKey GetEndEntityKey(this ObjectStateEntry entry, AssociationEndMember end) + { + end.ValidateBelongsTo(entry); + + AssociationEndMember[] ends = entry.GetAssociationEnds(); + + if (ends[0] == end) + { + return entry.UsableValues()[0] as EntityKey; + } + + return entry.UsableValues()[1] as EntityKey; + } + + public static NavigationProperty GetNavigationProperty(this ObjectStateEntry entry, AssociationEndMember end) + { + end.ValidateBelongsTo(entry); + + var otherEnd = entry.GetOtherAssociationEnd(end); + var relationshipType = entry.EntitySet.ElementType; + + EntityType elementType = GetInheritedEntityTypeByEntityName(entry, end.Name); + NavigationProperty property = + elementType.NavigationProperties.SingleOrDefault(p => p.RelationshipType == relationshipType && + p.FromEndMember == end && p.ToEndMember == otherEnd); + return property; + } + + static void ValidateBelongsTo(this AssociationEndMember end, ObjectStateEntry entry) + { + if (!entry.IsRelationship) + { + throw new ArgumentException("is not a relationship entry", "entry"); + } + + var fieldMetadata = + entry.UsableValues().DataRecordInfo.FieldMetadata; + if (fieldMetadata[0].FieldType as AssociationEndMember != end && + fieldMetadata[1].FieldType as AssociationEndMember != end) + { + throw new InvalidOperationException(string.Format( + "association end {0} does not participate in the " + + "relationship {1}", end, entry)); + } + } + + public static EntityType GetInheritedEntityType(ObjectStateEntry entry) + { + if (entry.Entity == null) + { + throw new ArgumentException("entity is null", "entry"); + } + string entityName = entry.Entity.GetType().Name; + return GetInheritedEntityTypeByEntityName(entry, entityName); + } + + static EntityType GetInheritedEntityTypeByEntityName(ObjectStateEntry entry, string entityName) + { + var stateEntryEdmType = + (EntityType) + entry.ObjectStateManager.MetadataWorkspace.GetType(entityName, entry.EntitySet.ElementType.NamespaceName, + DataSpace.CSpace); + + return stateEntryEdmType; + } + } +} diff --git a/Source/EntityFramework.Extended/EntityFramework.Extended.csproj b/Source/EntityFramework.Extended/EntityFramework.Extended.csproj index 5d35fef..bc5d68f 100644 --- a/Source/EntityFramework.Extended/EntityFramework.Extended.csproj +++ b/Source/EntityFramework.Extended/EntityFramework.Extended.csproj @@ -44,8 +44,7 @@ ..\EntityFramework.Extended.snk - - False + ..\packages\EntityFramework.5.0.0\lib\net45\EntityFramework.dll @@ -73,6 +72,7 @@ + diff --git a/Source/Samples/net40/Tracker.SqlCompact.Entities/Tracker.edmx b/Source/Samples/net40/Tracker.SqlCompact.Entities/Tracker.edmx index 4a03177..05f93e1 100644 --- a/Source/Samples/net40/Tracker.SqlCompact.Entities/Tracker.edmx +++ b/Source/Samples/net40/Tracker.SqlCompact.Entities/Tracker.edmx @@ -1,714 +1,714 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Samples/net40/Tracker.SqlServer.CodeFirst/Entity.csp b/Source/Samples/net40/Tracker.SqlServer.CodeFirst/Entity.csp index bc67dae..25ec03f 100644 --- a/Source/Samples/net40/Tracker.SqlServer.CodeFirst/Entity.csp +++ b/Source/Samples/net40/Tracker.SqlServer.CodeFirst/Entity.csp @@ -1,46 +1,46 @@ - - - - - - - - .\ - .\Entities - .\Mapping - Singular - Singular - Plural - Plural - - - sysdiagrams$ - - - False - - - ^(sp|tbl|udf|vw)_ - - - False - False - .\Queries - By - GetBy - Key - False - .\Mocks - True - Tracker.SqlServer.CodeFirst - - $(TrackerConnectionString) - SchemaExplorer.SqlSchemaProvider,SchemaExplorer.SqlSchemaProvider - - Tracker.SqlServer.CodeFirst.Mapping - Tracker.SqlServer.CodeFirst.Entities - Tracker.SqlServer.CodeFirst.Queries - Tracker.SqlServer.CodeFirst.Mocks - - + + + + + + + + .\ + .\Entities + .\Mapping + Singular + Singular + Plural + Plural + + + sysdiagrams$ + + + False + + + ^(sp|tbl|udf|vw)_ + + + False + False + .\Queries + By + GetBy + Key + False + .\Mocks + True + Tracker.SqlServer.CodeFirst + + $(TrackerConnectionString) + SchemaExplorer.SqlSchemaProvider,SchemaExplorer.SqlSchemaProvider + + Tracker.SqlServer.CodeFirst.Mapping + Tracker.SqlServer.CodeFirst.Entities + Tracker.SqlServer.CodeFirst.Queries + Tracker.SqlServer.CodeFirst.Mocks + + \ No newline at end of file diff --git a/Source/Samples/net40/Tracker.SqlServer.Entities/Tracker.edmx b/Source/Samples/net40/Tracker.SqlServer.Entities/Tracker.edmx index 29b2a35..0fca75c 100644 --- a/Source/Samples/net40/Tracker.SqlServer.Entities/Tracker.edmx +++ b/Source/Samples/net40/Tracker.SqlServer.Entities/Tracker.edmx @@ -1,703 +1,703 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file