Click here to Skip to main content
15,887,027 members
Articles / Programming Languages / XML

Traversing an object Tree to the end in EF4

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
30 Sep 2014CPOL3 min read 6.8K   4  
How to traverse an object tree to the end in EF4

Years ago, I read about Golden rule of Entity Framework from a blog post of Julia Lerman.As the Golden Rule says –

EF will not do anything that developer has not explicitly told you to do.

Some days ago, I came across an issue where I needed to load a full object graph will all its relationship. It means I need to load the object with all children and also with grand children of each child which means I needed to traverse the full tree of the object through their relationship/navigation until I will reached the leaf.

For example, the tree can be represented as:

XML
<Customer>
    ...
    <Orders>
      <Order>
        ...
        <Order_Details>
          </Order_Detail>
          ... .. .. .. .. .. So on.. .. .. (continue)
          </Order_Detail>
          ...
        </Order_Details>
      </Order>
      ...
    </Orders>
  ...
  </Customer>

Or can be represented as:

Customar

   |

   |

   -----Orders

          |

          |

          ------OrderDetails

|

|________.. .. .. .. So on .. ..(continue)

So I don’t know how much the depth of the object-tree will be and that’s why I am going to avoid the LINQ way. You can also do this by disabling the Lazy-loading but POCO entities do not have the same relationship requirements as objects that inherit from EntityObject, a slightly different process is required to load related objects. Ok, it is a very simple matter of Recursive solution by loading entity for each navigation object. To load the navigation property, I got a simple and useful method there:

C#
ObjectContext.LoadProperty Method (Object, String)

public void LoadProperty(
    Object entity,
    string navigationProperty)

Before going into my recursive method, I would like to talk about my Entity. All of my entities are child of “BaseDataContract” class. While traversing to the object, you can set a list of data type also to ignore while traversing them. Here, I check the navigation property whether that is a one or many relationship with current entity. If it is one – then I simply call the same method that it is currently in Recursive way; otherwise it calls another Generic method which calls the Traverse method for each entity in a collection for many relationship ends.

C#
private ObjectContext _ctx;

private bool IsInheritFromType(Type childType, Type parentType)
        {
            var type = childType;
            while (type.BaseType != null)
            {
                if (type.BaseType.Equals(parentType))
                    return true;

                type = type.BaseType;
            }
            return false;
        }    
        /// <summary>
        /// Traverse to the end and load the related entity. 
        /// </summary>
        /// <param name="entity">
        /// Set the entity from which start traversing</param>
        /// <param name="parentType">Set the Parent to 
        /// ignore the recursion. For root entity, set it null</param>
        /// <param name="ignoreType">
        /// Some Type of entity I want to Ignore</param>
        /// <returns></returns>
        public void TraverseToTheEnd<T>(T entity, 
        Type parentType =null ,Type[] ignoreType =null ) where T : BaseDataContract
        {
            if (null != entity)
            {
                var navigationProperties = _ctx.GetNavigationProperty<T>();
                if (null != navigationProperties)
                {
                    foreach (var navigationProperty in navigationProperties)
                    {
                        var info = entity.GetType().GetProperty(navigationProperty.Name);

                        if (null != info)
                        {
                            Type t = info.PropertyType;
                            if (null == ignoreType || !Array.Exists(ignoreType, i => i.Equals(t)))
                            {
                                if (((AssociationType) 
                                navigationProperty.RelationshipType).IsForeignKey)
                                {
                                    _ctx.LoadProperty(entity, navigationProperty.Name);

                                    var value = info.GetValue(entity, null);
                                    if (value != null)
                                    {
                                        Type baseType;
                                        if (!t.IsGenericType)
                                        {
                                            #region One to One or Zero

                                            baseType = t.BaseType;
                                            if (null != baseType && 
                                            (parentType == null || !t.Equals(parentType))
                                                //avoid the recursion, dont back to parent 
                                                && baseType.BaseType != null
                                                && IsInheritFromType(baseType, 
                                                typeof (BaseDataContract)))
                                            {
                                                //Traverse each child
                                                MethodInfo method = 
                                                this.GetType().GetMethod("TraverseToTheEnd");
                                                MethodInfo generic = method.MakeGenericMethod(t);
                                                generic.Invoke(this, 
                                                new object[] {value, typeof (T), ignoreType});
                                            }

                                            #endregion
                                        }
                                        else
                                        {
                                            #region One to Many

                                            Type[] genericBases = t.GetGenericArguments();
                                            // Q&D: pick the first one
                                            baseType = genericBases[0];
                                            if (null != baseType && 
                                            (parentType == null || !baseType.Equals(parentType))
                                                //avoid the recursion, dont back to parent
                                                && baseType.BaseType != null &&
                                                IsInheritFromType(baseType, typeof (BaseDataContract)))
                                            {
                                                //Traverse each child
                                                MethodInfo method =
                                                    this.GetType().GetMethod
                                                    ("TraverseToTheEndForCollection");
                                                MethodInfo generic = method.MakeGenericMethod(baseType);
                                                generic.Invoke(this, new object[] 
                                                {value, typeof (T), ignoreType});
                                            }

                                            #endregion
                                        }

                                    } //end if 
                                } //end if
                            }//end if                            
                        }
                    }//end foreach
                }//end if
            }//end if 
        }
        /// <summary>
        /// Traverse each entity of a collection to their end and load the related entity. 
        /// </summary>
        /// <param name="entities">Set the collection 
        /// of entity for traversing each entity</param>
        /// <param name="parentType">Set the Parent to 
        /// ignore the recurtion. For root entity, set it null</param>
        /// <param name="ignoreType">Some Type of entity I want to Ignore</param>
        /// <returns></returns>
        public void TraverseToTheEndForCollection<T>
        (FixupCollection<T> entities, Type parentType =null ,Type[] ignoreType =null )
           where T : BaseDataContract
        {
            if (null != entities)
            {
                for (int i = 0; i < entities.Count; i++)
                {
                    TraverseToTheEnd(entities[i], parentType, ignoreType);
                }
            }
        }

To load the navigation property value, I just call the above method – loadProperty which is provided by the ObjectContext.

C#
_ctx.LoadProperty(entity, navigationProperty.Name);

var value = info.GetValue(entity, null);

After that, check the navigation property type whether that is generic type or not. In EF, Generic navigation type is used for Collection of entity in case of Many End and on the other hand, normal property type for One or Zero End. Here. The method – IsInheritFromType is nothing but check for BaseType to some desired parent Type. This has been checked to determine whether it is database object or not. To call a Generic method:

C#
MethodInfo method = this.GetType().GetMethod("TraverseToTheEnd");

MethodInfo generic = method.MakeGenericMethod(t);

generic.Invoke(this, new object[] {value, typeof (T), ignoreType});

And TraverseToTheEndForCollection method has been called in the same way with the parameter of child/navigation property value parent type and other parameters.

Here, I need to create some extension of ObjectContext to get all navigation properties of an entity by manipulating the metadata information. In GetNavigationProperty method, we need to retrieve the entity set name (that’s why we need the GetEntitySet method) and from its type members it checks for nevigationProperty as BuiltInTypeKind.

C#
public static EntitySetBase GetEntitySet(this ObjectContext context, Type entityType)
      {
          if (context == null)
          {
              throw new ArgumentNullException("context");
          }
          if (entityType == null)
          {
              throw new ArgumentNullException("entityType");
          }
          EntityContainer container = context.MetadataWorkspace.GetEntityContainer
          (context.DefaultContainerName, DataSpace.CSpace);
          EntitySetBase entitySet = container.BaseEntitySets.Where
          (item => item.ElementType.Name.Equals(entityType.Name))
                                                            .FirstOrDefault();
          return entitySet;
      }

      public static List<NavigationProperty> GetNavigationProperty<T>(
      this ObjectContext context)
      {
          var containerName = context.DefaultContainerName;
          var model = DataSpace.CSpace;
          var workspace = context.MetadataWorkspace;
          var container = workspace.GetEntityContainer(containerName, model);
          EntitySetBase entitySet = context.GetEntitySet(typeof(T));

          if (entitySet == null)
              return null;

          //materialize nav props for testing
          var navigationProps = entitySet.ElementType.Members
              .Where(m => m.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty
              )
              .Cast<NavigationProperty>()
              .ToList();

          return navigationProps;
      }

Ok, that is it.

Yes, you can ask me why I am going to such great length. The answer is - because I want to backup the object tree into a backup file and later on, it will be attached to database that might be the same database or another one with the same schema. It will not override any entity if any data of this object-tree already exists, if not, then it will insert the object with related object as it is currently in its own. But that’s a different story. This method gives an object tree with all its related objects to the leaf and I can do whatever I want. For now, I would like to serialize this object in a file and on the next post, I will try to re-attach the object to database. Thanks for reading and I hope you like it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader PracticePRO Software Systems Inc
United States United States
In my childhood, my uncle has shown me how to see the cloud in a close look and I understand that one can draw some elements of the Earth in the sky-canvas if he/she wants to. After that the cloud becomes closer to me and It teaches me one thing that, a deeper-look to something will give you some clues to draw your imagination. You can able to see that one which you have build-up in your mind.

Years past, I have started my career as a software engineer and has been looking for passion in my coding and development which I should be to enjoy my profession and has started asking myself- 'am I doing any engineering here?!' Is my code becoming that thing which I have designed in my mind? So to find that answer I have tried that old solution here... I have decided to come closer to my code and start analyzing them. And it is really working for me and at least it gives me the confidence that I can build something that I really want to. I can draw my thinking there through my code and can build-up my vision that I have designed in my mind. It also helps me to think out of the box, solve each problems by making blocks and make me careful on each steps.

• Morshed's Technical Blog site: http://morshedanwar.wordpress.com/

• Morshed's Technical articles those are published in Codeproject site: http://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=2992452

• Morshed's Linkedin profile: http://www.linkedin.com/in/morshedanwar

• Morshed's Facebook Profile : http://www.facebook.com/morshed.pulok

Beside all these I like to do - photography and music. Here is my Flickr photos : http://www.flickr.com/photos/morshed_anwar/

Comments and Discussions

 
-- There are no messages in this forum --