How to get original Entity from ChangeTracker

20,422

Solution 1

Override SaveChanges of DbContext or just access ChangeTracker from the context:

foreach (var entry in context.ChangeTracker.Entries<Foo>())
{
    if (entry.State == System.Data.EntityState.Modified)
    {
        // use entry.OriginalValues
        Foo originalFoo = CreateWithValues<Foo>(entry.OriginalValues);
    }
}

Here is a method which will create a new entity with the original values. Thus all entities should have a parameterless public constructor, you can simply construct an instance with new:

private T CreateWithValues<T>(DbPropertyValues values)
    where T : new()
{
    T entity = new T();
    Type type = typeof(T);

    foreach (var name in values.PropertyNames)
    {
        var property = type.GetProperty(name);
        property.SetValue(entity, values.GetValue<object>(name));
    }

    return entity;
}

Solution 2

Nice. Here is a slightly modified version that will handle complex properties:

public static TEntity GetOriginal<TEntity>(this DbContext ctx, TEntity updatedEntity) where TEntity : class
    {
        Func<DbPropertyValues, Type, object> getOriginal = null;
        getOriginal = (originalValues, type) =>
             {
                 object original = Activator.CreateInstance(type, true);
                 foreach (var ptyName in originalValues.PropertyNames)
                 {
                     var property = type.GetProperty(ptyName);
                     object value = originalValues[ptyName];
                     if (value is DbPropertyValues) //nested complex object
                     {
                         property.SetValue(original, getOriginal(value as DbPropertyValues, property.PropertyType));
                     }
                     else
                     {
                         property.SetValue(original, value);
                     }
                 }
                 return original;
             };
        return (TEntity)getOriginal(ctx.Entry(updatedEntity).OriginalValues, typeof(TEntity));
    }

Solution 3

I would suggest clone entities on materialization and attach them to second context to keep whole original objects graph (if you need it of course). You can make them all ICloneable by modifying T4 template.

Share:
20,422
Eric
Author by

Eric

Updated on September 14, 2020

Comments

  • Eric
    Eric over 3 years

    Is there a way to get the original Entity itself from the ChangeTracker (rather than just the original values)?

    If the State is Modified, then I suppose I could do this:

    // Get the DbEntityEntry from the DbContext.ChangeTracker...
    
    // Store the current values
    var currentValues = entry.CurrentValues.Clone();
    
    // Set to the original values
    entry.CurrentValues.SetValues(entry.OriginalValues.Clone());
    
    // Now we have the original entity
    Foo entity = (Foo)entry.Entity;
    
    // Do something with it...
    
    // Restore the current values
    entry.CurrentValues.SetValues(currentValues);
    

    But this doesn't seem very nice, and I'm sure there are problems with it that I don't know about... Is there a better way?

    I'm using Entity Framework 6.

  • Eric
    Eric about 11 years
    I may be missing a subtlety, but I believe I know how to do this (this is just getting the original values, right?) I want an actual strongly typed representation of the original entity - not just the original values.
  • Sergey Berezovskiy
    Sergey Berezovskiy about 11 years
    @Eric you want to have entity object with original properties values?
  • Eric
    Eric about 11 years
    Yes, that's correct. Perhaps a way to construct an entity given a set of original values...
  • Sergey Berezovskiy
    Sergey Berezovskiy about 11 years
    @Eric done, I've wrote method which creates entity and set property values via reflection
  • Brikesh Kumar
    Brikesh Kumar over 10 years
    Thanks @Clement. I had this issue and spent lot of time to get the old ones..First tried to return totally disconnected objects. BUt it had its own set of issue while updating. This one works, need to test some more scenarios though
  • Brikesh Kumar
    Brikesh Kumar over 10 years
    Hey @Clement. This one doesn't hydrate the navigational properties. Any clue?
  • Clement
    Clement over 10 years
    Not sure, I haven't tested with nav properties. Because it's using Activator.CreateInstance, I don't expect lazy loading of navigational properties to work (since it relies on a runtime proxy type generated by EF). You might be able to change this code to handle them...
  • Brikesh Kumar
    Brikesh Kumar over 10 years
    Thanks @Clement. I load the the graph one at a time, didn't modify to generically populate the navigation properties though.
  • Mario Duarte
    Mario Duarte over 8 years
    This only returns a type. I couldn't get the original values.