I have a base class that I inherit from that has two zero to many relationships with other entities:
public abstract class WebObject
{
public WebObject()
{
RelatedTags = new List<Tag>();
RelatedWebObjects = new List<WebObject>();
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string MetaKeywords { get; set; }
public string MetaDescription { get; set; }
[InverseProperty("WebObjects")]
public virtual WebSite WebSite { get; set; }
[Required(ErrorMessage = "Every WebObject must be associated with a WebSite.")]
public Guid WebSiteId { get; set; }
public virtual ICollection<Tag> RelatedTags { get; set; }
public IList<Guid> RelatedTagIds { get; set; }
public virtual ICollection<WebObject> RelatedWebObjects { get; set; }
public IList<Guid> RelatedWebObjectIds { get; set; }
}
I am having difficulty getting the original values for these relationships (RelatedWebObjects & RelatedTags) when looking at the entities using ChangeTracker during SaveChanges. I can see all of the scalar values before and after, and I can see the new relationships, but I cannot see the old ones. I've tried using the Member and Collection methods, but those only show me the current values; not the old. Also I don't like using those because it requires me to know the name of the navigation property, which isn't generic enough.
I can find the related objects whose relationship is being changed, but of course the values within those related objects isn't changing, so that isn't any help either.
Is there some clean way for me to track the previous relationships of an entity during SaveChanges with ChangeTracker?
Below is the section of code that I'm working on:
public override int SaveChanges()
{
List<AuditObject> auditTrailList = new List<AuditObject>();
foreach (DbEntityEntry entity in ChangeTracker.Entries().Where(obj => { return obj.State == EntityState.Added || obj.State == EntityState.Modified || obj.State == EntityState.Deleted; }))
{
if (!(entity.Entity is AuditObject))
{
AuditObject auditObject = new AuditObject();
auditObject.Id = Guid.NewGuid();
auditObject.RevisionStamp = DateTime.Now;
auditObject.UserName = HttpContext.Current.User.Identity.Name;
auditObject.EntityType = Utilities.GetCleanClassNameIfProxyClass(entity.Entity.GetType().Name);
if (entity.State == EntityState.Added)
auditObject.Action = EntityState.Added.ToString();
else if (entity.State == EntityState.Modified)
auditObject.Action = EntityState.Modified.ToString();
else if (entity.State == EntityState.Deleted)
auditObject.Action = EntityState.Deleted.ToString();
DbMemberEntry t1 = entity.Member("RelatedWebObjects");
// cannot find original relationship collection...
DbCollectionEntry t2 = entity.Collection("RelatedWebObjects");
// cannot find original relationship collection...
if (entity.State == EntityState.Added || entity.State == EntityState.Modified)
{
XDocument currentValues = new XDocument(new XElement(auditObject.EntityType));
foreach (string propertyName in entity.CurrentValues.PropertyNames)
{
currentValues.Root.Add(new XElement(propertyName, entity.CurrentValues[propertyName]));
}
auditObject.NewData = Regex.Replace(currentValues.ToString(), @"\r\n+", " ");
}
if (entity.State == EntityState.Modified || entity.State == EntityState.Deleted)
{
XDocument originalValues = new XDocument(new XElement(auditObject.EntityType));
foreach (string propertyName in entity.OriginalValues.PropertyNames)
{
originalValues.Root.Add(new XElement(propertyName, entity.OriginalValues[propertyName]));
}
auditObject.OldData = Regex.Replace(originalValues.ToString(), @"\r\n+", " ");
}
auditTrailList.Add(auditObject);
}
}
foreach (var audit in auditTrailList)
this.AuditObjects.Add(audit);
return base.SaveChanges();
}
Well this is little bit difficult. First of all you have to differ two types of relationships offered by EF:
Now if you want to know previous value of foreign key association you just need to track the changes in dependent entity where you have exposed foreign key property - this is exactly the same as tracking any other property change.
If you want to track changes in an independent association the situation will become harder because DbContext API doesn't provide operations to track them. You must revert back to ObjectContext
API and its ObjectStateManager
.
ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
foreach (ObjectStateEntry entry = objectContext.ObjectStateManager
.GetObjectStateEntries(~EntityState.Detached)
.Where(e => e.IsRelationship))
{
// Track changes here
}
Now you have access to ObjectStateEntry
instances for the relationship. These instances should never have state Modified
. They will be either Added
, Deleted
or Unchanged
because "modification" is processed as deletion of old relation and adding a new one. ObjectStateEntry
also contains CurrentValues
and OriginalValues
collections. These collections should also contain two items each representing EntityKey
of entity on one side of the relation.
Because EF change tracks every object in your graph, you can always pass any instance in the graph to the change tracker and it will give you the change tracking values. For example, the following code will get the Original/Current values of the AuditObject's navigation property:
DbMemberEntry t1 = entity.Member("RelatedWebObjects");
// cannot find original relationship collection....
AuditObject currentAuditObject = (AuditObject) entity;
var currValues = this.Entry(currentAuditObject.RelatedWebObjects).CurrentValues;
var orgValues = this.Entry(currentAuditObject.RelatedWebObjects).OriginalValues;
Or you can apply the same trick when you are dealing with a collection type navigation property:
DbCollectionEntry t2 = entity.Collection("RelatedWebObjects");
// cannot find original relationship collection....
foreach (WebObject item in currentAuditObject.RelatedWebObjects)
{
var currValues = this.Entry(item).CurrentValues;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With