CodingPleasure

cultivate passion for everything else that goes on around programming

Audit Trail with EntityReferences in Entity Framework 3.5

Tracking changes in Entity Framework 3.5 is pretty straight forward. I used the techniques described here: Audit Trail Using EF Part 1

Everything was fine except that the code in this article did not track changes to EntityReferences. So given an object A that has a reference to object B, the Entity Framework does not mark the reference from A to B as changed. Instead, when the reference changes, it deletes the previous reference and adds the new reference as an ObjectStateEntry. This way, when calling ObjectStateManager.GetObjectStateEntries(), there will be 2 objects returned, one with the EntityState.Deleted state and one with the EntityState.Added state. Additionally, the ObjectStateEntry.GetModifiedProperties() method does not return any information about the change of the reference.

So I adapted the code from the article to retrieve the changed reference values too. The following code shows how I did that, based on the code from the original article.

   1: /// <summary>
   2: /// Gets the entity properties as XML, either the original values or the current values.
   3: /// </summary>
   4: /// <param name="entry">The entry.</param>
   5: /// <param name="isOrginal">if set to <c>true</c> then the original values will be serialized.</param>
   6: /// <returns>XML string of serialized entity</returns>
   7: private string GetEntityPropertiesAsXml(ObjectStateEntry entry, bool isOrginal)
   8: {
   9:     if (entry.Entity is EntityObject)
  10:     {
  11:         object target = HelperClasses.CloneEntity((EntityObject)entry.Entity);
  12:         foreach (string propName in entry.GetModifiedProperties())
  13:         {
  14:             object setterValue = null;
  15:             if (isOrginal)
  16:             {
  17:                 //Get original value
  18:                 setterValue = entry.OriginalValues[propName];
  19:             }
  20:             else
  21:             {
  22:                 //Get original value
  23:                 setterValue = entry.CurrentValues[propName];
  24:             }
  25:  
  26:             //Find property to update
  27:             PropertyInfo propInfo = target.GetType().GetProperty(propName);
  28:  
  29:             //update property with original value
  30:             if (setterValue == DBNull.Value)
  31:             {
  32:                 setterValue = null;
  33:             }
  34:             propInfo.SetValue(target, setterValue, null);
  35:         }
  36:  
  37:         // references are not returned by GetModifiedProperties(), so we need to search through all
  38:         // object state entries for those matching our properties and get the deleted ones
  39:         var deletedRelations = entry.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).Where(ose => ose.IsRelationship);
  40:         var addedRelations = entry.ObjectStateManager.GetObjectStateEntries(EntityState.Added).Where(ose => ose.IsRelationship);
  41:         foreach (PropertyInfo pi in target.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(pi => pi.PropertyType.BaseType == typeof(EntityReference)))
  42:         {
  43:             // try to get the original values
  44:             if (isOrginal)
  45:             {
  46:                 EntityReference currentReference = (EntityReference)pi.GetValue(target, null);
  47:  
  48:                 // find the value of the old relation in the deletedRelations
  49:                 var oldRelation = deletedRelations
  50:                     .Where(relation => relation.EntitySet.ElementType.FullName == currentReference.RelationshipName)
  51:                     .FirstOrDefault();
  52:  
  53:                 if (oldRelation != null)
  54:                 {
  55:                     // set the current value to the old reference
  56:                     currentReference.EntityKey = (oldRelation.OriginalValues[0] as EntityKey);
  57:                 }
  58:                 else
  59:                 {
  60:                     var addedRelation = addedRelations
  61:                         .Where(relation => relation.EntitySet.ElementType.FullName == currentReference.RelationshipName)
  62:                         .FirstOrDefault();
  63:  
  64:                     // if an added relation was found, but a deleted one was not found
  65:                     if (addedRelation != null)
  66:                     {
  67:                         // object did not have any relation attached previously
  68:                         currentReference.EntityKey = null;
  69:                     }
  70:                 }
  71:             }
  72:         }
  73:  
  74:         XmlSerializer formatter = new XmlSerializer(target.GetType());
  75:         XDocument document = new XDocument();
  76:         using (XmlWriter xmlWriter = document.CreateWriter())
  77:         {
  78:             formatter.Serialize(xmlWriter, target);
  79:         }
  80:  
  81:         return document.Root.ToString();
  82:     }
  83:  
  84:     return null;
  85:  
  86: }

The interesting part begins at line 39, where I try to find all relationships, added or deleted, that are related to the given entry. By finding the deleted references we are able to set the original value of the EntityKey. If no deleted reference is found, we try to find an added relation. If an added relation exists, but no deleted one, than we can assume that the reference was not set before the change, so we set it’s EntityKey to null.

Thus we can store our OldData and our NewData into the audit trail database.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: