Thursday, July 7, 2011

Entry Extensions

Entity Framework 4.1 CodeFirst is quite a bit of fun to use, but tucks away some of the useful database functionality. One useful thing is to know if a property on a database entry has been changed since it was loaded. DbEntityEntry exposes OriginalValues and CurrentValues, but actually comparing the two can be streamlined with Expressions.


public static class DbEntityEntryExtensions
    /// <summary>
    /// Determine if a property has changed since being loaded.
    /// <remarks>
    /// If comparing a property that is a sub-object against another instance, it will always throw true (changed).
    /// Complex properties should implement IEquatable to properly handle how equality is determined.
    /// </remarks>
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TProperty"></typeparam>
    /// <param name="entity"></param>
    /// <param name="expression"></param>
    /// <returns></returns>
    public static bool PropertyChanged<TEntity, TProperty>(this DbEntityEntry<TEntity> entity, Expression<Func<TEntity, TProperty>> expression)
        where TEntity : class
        var name = ((MemberExpression)expression.Body).Member.Name;

        return !entity.CurrentValues.GetValue<TProperty>(name).Equals(entity.OriginalValues.GetValue<TProperty>(name));

Determining if a loaded POCO property has changed is pretty easy:

var o = MyDbContext.TestClasses.Find(1);
o.Id = 2;

var changed = MyDbContext.Entry(o).PropertyChanged(c => c.Id);   // True

This works fine for primitive types such as int, string and float. However, if your property is another object, it won't work. This is because at it's heart, we're calling Equals.

class TestClass
    public int Id { get; set; }

var a = new TestClass { Id = 1 };
var b = new TestClass { Id = 1 };

var equal = a.Equals(b);  // False!

To show this with EF 4.1...

class TestClass
    public int Id { get; set; }
    public AssociationClass Association { get; set; }

class AssociationClass
    public int Id { get; set; }

// Assume that the stored o.Association has an ID of 1
var o = var o = MyDbContext.TestClasses.Find(1);

// Set to a new, identical instance of the class
o.Association = new AssociationClass { Id = 1 };

// This is supposed to be false.  The database holds that TestClass:1
// is associated with AssociationClass:1.  However, since the objects
// are different, Equals will return false, and PropertyChanged will
// return true - thinking that the association has changed.
var changed = MyDbContext.Entry(o).PropertyChanged(c => c.Association);   // True (wrong)

There is probably a way to check if the property is an association and check possible entity keys, but that's for another time.

No comments:

Post a Comment