Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ChangeTracker Entity Framework 4.1 - Original Values of Related Objects

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();
    }
like image 834
DMC Avatar asked Aug 16 '11 13:08

DMC


2 Answers

Well this is little bit difficult. First of all you have to differ two types of relationships offered by EF:

  • Independent association (all many-to-many relations and some one-to-many)
  • Foreign key association (all one-to-one relations and some one-to-many)

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.

like image 163
Ladislav Mrnka Avatar answered Oct 10 '22 01:10

Ladislav Mrnka


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;
}
like image 22
Morteza Manavi Avatar answered Oct 10 '22 00:10

Morteza Manavi