Entity Framework and RIA Services do not allow Entities to be added/removed from cross containers. In order to move Entity from one DomainContext/ObjectContext to another DomainContext/ObjectContext, we need to recursively detach the object graph and attach it to other DomainContext/ObjectContext.

Object Graph

Single entity can be easily detached and attached from EntitySet where it belongs, but the problem comes when navigation properties of entity are not empty. Entity object along with navigation properties is called Object Graph because if you notice, navigation property’s navigation property will contain reference to same entity and that will result in endless recursive code for Detach/Attach.

An entity with the same identity already exists in the EntitySet

When you try to detach entity from old DomainContext/ObjectContext and attach it to DomainContext/ObjectContext, it may give you an error that entity with same identity already exists and it will throw an exception. In this case we will just simply reuse the existing entity instead of attaching the entity we have.

Entity cannot be attached to this EntityContainer because it is already attached to another EntityContainer

In case of ObjectGraph, your root level entity is already detached, but navigation properties are not detached, and while you try to attach your root level entity, it will throw same error for entities that exist in navigation properties.  Because detach method does not recursively detach every entity from navigation properties.

Attach/Detach Extension Methods

Finally after brainstorming little, I made following class that will allow you to recursively detach/attach object graphs from DomainContext. You can replace DomainContext to ObjectContext to use it inside Entity Framework.

DomainContext Extensions
  1. /// <summary>
  2. /// DomainContext Extensions
  3. /// </summary>
  4. public static class DomainContextExtensions {
  5.  
  6.     /// <summary>
  7.     /// Recursively Attaches entity loaded from Other DomainContext to
  8.     /// current specified DomainContext
  9.     /// </summary>
  10.     /// <param name="context">DomainContext where entity will be attached</param>
  11.     /// <param name="entity">Entity loaded from other DomainContext</param>
  12.     /// <returns></returns>
  13.     public static Entity Attach(this DomainContext context, Entity entity)
  14.     {
  15.         if (entity == null || entity.EntityState != EntityState.Detached)
  16.             return entity;
  17.  
  18.         Entity newEntity = entity;
  19.  
  20.         Entity[] list = new Entity[] { entity };
  21.         foreach (Entity c in context.EntityContainer.LoadEntities(list,
  22.             LoadBehavior.MergeIntoCurrent))
  23.         {
  24.             newEntity = c;
  25.             break;
  26.         }
  27.  
  28.         // recursively attach all entities..
  29.         Type entityType = typeof(Entity);
  30.  
  31.         // get all navigation properties…
  32.         Type type = entity.GetType();
  33.         foreach (var item in type.GetProperties())
  34.         {
  35.             if (entityType.IsAssignableFrom(item.PropertyType))
  36.             {
  37.                 Entity navEntity = Attach(context, item.GetValue(entity, null)
  38.                     as Entity);
  39.                 item.SetValue(newEntity,navEntity, null);
  40.                 continue;
  41.             }
  42.             if (item.PropertyType.Name.StartsWith("EntityCollection"))
  43.             {
  44.                 IEnumerable coll = item.GetValue(entity, null) as IEnumerable;
  45.                 List<Entity> newList = new List<Entity>();
  46.                 foreach (Entity child in coll)
  47.                 {
  48.                     newList.Add(Attach(context, child));
  49.                 }
  50.                 dynamic dcoll = item.GetValue(newEntity,null);
  51.                 foreach (dynamic child in newList)
  52.                 {
  53.                     dcoll.Add(child);
  54.                 }
  55.             }
  56.         }
  57.         return newEntity;
  58.     }
  59.  
  60.     /// <summary>
  61.     /// Recursively detaches entities from DomainContext, this
  62.     /// method detaches every navigation properties
  63.     /// of current Entity as well.
  64.     /// </summary>
  65.     /// <param name="context"></param>
  66.     /// <param name="entity"></param>
  67.     public static void Detach(this DomainContext context, Entity entity)
  68.     {
  69.         if (entity == null || entity.EntityState == EntityState.Detached)
  70.             return;
  71.         EntitySet nes = context.EntityContainer.GetEntitySet(entity.GetType());
  72.         nes.Detach(entity);
  73.  
  74.         Type entityType = typeof(Entity);
  75.  
  76.         // get all navigation properties…
  77.         Type type = entity.GetType();
  78.         foreach (var item in type.GetProperties())
  79.         {
  80.             if (entityType.IsAssignableFrom(item.PropertyType))
  81.             {
  82.                 Detach(context,item.GetValue(entity, null) as Entity);
  83.                 continue;
  84.             }
  85.             if (item.PropertyType.Name.StartsWith("EntityCollection"))
  86.             {
  87.                 IEnumerable coll = item.GetValue(entity, null) as IEnumerable;
  88.                 foreach (Entity child in coll)
  89.                 {
  90.                     Detach(context,child);
  91.                 }
  92.             }
  93.         }
  94.     }
  95.  
  96. }

Share
Tagged with: