Recursive ObjectContext Detaching in Entity Framework

By: on May 30, 2015

Entity Framework (EF) is an ORM framework often used with ASP.net applications, such as on Daylight.

Object states in EF are tracked by the ObjectStateManager part of its ObjectContext. Whenever you modify, create, or delete any Entities, ObjectStateManager will keep track of it so that when you decide to Save your Entities to your database, for example, it knows what kind of SQL queries to construct.

Consider you have a two types of Entites, Foo and Bar where Foo contains a navigation property of type Bar. Whenever you call the constructor of Foo, a Bar object will always be created. ObjectStateManager will detect this and add both the Foo and Bar objects created into its object graph. However, there might be times when you don’t want this to happen.

Consider a ViewModel that allows the user to optionally create a new Foo entity. You really do not want the Foo Entity to enter the object graph (and thus the Bar entity) until you are sure that the user wants to create a new Foo entity. But sometimes, due to various reasons, you have to create a stub Foo entity during the creation of the ViewModel. By doing so, the Foo entity will have entered the object graph, and if the user decides to not want to create the entity, you will have a spurious Foo entity floating around in your database.

ObjectContext has a Detach method that allows you to detach any entities from the ObjectContext, thereby removing its state from the ObjectStateManager. The downside to this is that the method is not recursive — that is the method will not recursively detach any children entities. For our example, you can simply detach the child Bar entity as well. For more complex entities, you might have a bigger headache.

We encountered this problem in Daylight where, due to various reasons, we had to detach a stub Entity at the point of Saving. The stub entity was required to render the page (because it provided the facilities to filter out irrelevant enum values) and would have required a significant change in the rest of the application. It was easier to recursively detach the entity than to change a non-insignificant portion of the application.

We implemented the Detach method as part of our wrapper class around ObjectContext using some reflection:

        // context is our ObjectContext
        public void Detach<T>(T obj, bool recursive = false)
        {
            if (!recursive)
            {
                context.Detach(obj);
            }
            else
            {
                detach(obj, new HashSet<object>());
            }
        }

        private void detach<T>(T obj, ISet<object> objectSet)
        {
            if (obj == null) return;
            ObjectStateEntry objectState;
            if (!context.ObjectStateManager.TryGetObjectStateEntry(obj, out objectState))
                return;
            if (objectState.State == EntityState.Deleted || objectState.State == EntityState.Unchanged)
                return;

            // This is to prevent an infinite recursion when the child object has a navigation property
            // that points back to the parent
            if (!objectSet.Add(obj))
                return;

            // Recursively detach navigation property
            foreach (var property in obj.GetNavigationProperties())
            {
                if (property.IsEntityCollection())
                {
                    var collection = property.GetValue(obj, null) as IEnumerable<object>;
                    if (collection == null) continue;
                    foreach (var child in collection.ToList())
                    {
                        detach(child, objectSet);
                    }
                }
                else
                {
                    detach(property.GetValue(obj, null), objectSet);
                }
            }
            context.Detach(obj);
        }

We also wrote some simple extension methods to make the reflection code slightly cleaner:

        // Navigation properties have the EdmRelationshipNavigationPropertyAttribute attribute
        public static IEnumerable<PropertyInfo> GetNavigationProperties(this Type type)
        {
            return type.GetProperties()
                .Where(p => p.HasAttribute<EdmRelationshipNavigationPropertyAttribute>());
        }

        public static bool IsEntityCollection(this PropertyInfo property)
        {
            return property.PropertyType.IsGenericType &&
                   property.PropertyType.GetGenericTypeDefinition() == typeof (EntityCollection<>);
        }

The code above works well for our application, but might require some tweaking if you use other features of EF such as complex objects. Hopefully, this will serve as a useful starting point if you ever find yourself in the same situation.

Share

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*