static void

EF Code First - Generically setting entity references to unchanged status

Published Monday 19 March 2012

Last time I was adding a new record with a reference to a dummy record. I marked the reference as Unchanged so Code First wouldn't try to validate or save it.

var johnCarter = new Movie() { Title = "John Carter" };
johnCarter.DirectorId = andrewStantonId;
context.Movies.Add(johnCarter);
//after it's added, change the status of the reference
context.Entry(johnCarter.Director).State =
EntityState.Unchanged;
context.SaveChanges();

Can you set all the references on any entity?

var johnCarter = new Movie() { Title = "John Carter" };
johnCarter.DirectorId = andrewStantonId;
context.Movies.Add(johnCarter);
//after it's added, change the status of the reference
MarkNavigationPropertiesUnchanged(johnCarter);
context.SaveChanges();

We have to look into the underlying EF model.

private static void MarkNavigationPropertiesUnchanged<T>(DbContext context, T entity)
    where T : class
{
    var objectContext = ((IObjectContextAdapter)context).ObjectContext;
    var objectSet = objectContext.CreateObjectSet<T>();
    var elementType = objectSet.EntitySet.ElementType;
    var navigationProperties = elementType.NavigationProperties;
    //the references
    var references = from navigationProperty in navigationProperties
                        let end = navigationProperty.ToEndMember
                        where end.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne ||
                        end.RelationshipMultiplicity == RelationshipMultiplicity.One
                        select navigationProperty.Name;
    //NB: We don't check Collections. EF wants to handle the object graph.
 
    var parentEntityState = context.Entry(entity).State;
    foreach (var navigationProperty in references)
    {
        //if it's modified but not loaded, don't need to touch it
        if (parentEntityState == EntityState.Modified &&
            !context.Entry(entity).Reference(navigationProperty).IsLoaded)
            continue;
        var propertyInfo = typeof(T).GetProperty(navigationProperty);
        var value = propertyInfo.GetValue(entity, null);
        context.Entry(value).State = EntityState.Unchanged;
    }
}

This code only fixes the references to single entities (like movie.Director) - not collections (like director.Movies). It's possible to discover and iterate the collections to change their status, but you'll likely get exceptions from EF because its model is broken.

Previously: EF Code First - Using dummy references (18 Mar 2012)