Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Cloning the Entity object and all related children using the Entity Framework

4.75/5 (7 votes)
8 May 2012CPOL4 min read 155.7K   3.5K  
Cloning the Entity object and all its children using the Entity Framework, using LINQ and Reflection.

Introduction

There are a lot of real world scenarios where the requirement is to copy huge records and all the related records using simple interfaces, which can be reused with very little changes rather than creating all over again. Here in this article, we will see a simple way to crack this problem in the LINQ to Entities world. This article tries to solve the following problems:

  • Entity lazy loading: Loads the entity and all its associations in one method call.
  • Entity cloning: How to clone an Entity and all its associations in LINQ to Entities (Entity Framework). This involves popping the data into the database back after cloning.

Note:new version fixed error 'An Object w with Same Key  is already exists '

Background

This article uses the following techniques:

  • Serialization - For cloning an Entity.
  • Reflection - For dynamically invoking methods and accessing attributes. Reflection is used to implement loading the child objects and clearing the entity reference from the cloned object.
  • LINQ - LINQ is used across the implementation.
  • Extension Methods - The Entity object is extended to implement the described functionalities.

This article will focus more on the solution and logic rather than explaining the basics of these techniques.

Using the code

The sample project is a simple Employee master creation application with some CRUD operations. This was implemented with ASP.NET MVC and the Entity Framework.

ER diagram

Folder and file details in the attached project

  • DbScript/efcloningsample.sql - DB scripts for creating the DB objects.
  • CLonehelper.cs - This class has Extension Methods for loading the Entity, cloning the Entity, and clearing the Entity reference.
  • View/ Employeemaster - All the views have content for the Model defined in the database.
  • HomeController - Controller for CRUD operations for Employee.
  • Models/ EmpModel.edmx - EF model created from the database.

Implementation details on CloneHelper.CS

The CloneHelper class has all the logic and implementation. Now we will look at its methods and its role in the implemenatation.

Clone

This is an Extension Method to EntityObject. This uses a DataContractSerializer to serialize the object. The cloned Entity will be returned back to the caller.

C#
public static T Clone<t>(this T source) where T:EntityObject  { 
    var obj = new System.Runtime.Serialization.DataContractSerializer(typeof(T)); 
    using (var stream = new System.IO.MemoryStream())
    {
        obj.WriteObject(stream, source);
        stream.Seek(0, System.IO.SeekOrigin.Begin);
        return (T)obj.ReadObject(stream); 
    } 
}
LoadAllChild

LoadAllChild will load all the associated child objects of the base Entity from the database. The LINQ query will get the child objects, and using Reflection, the Load method is invoked on all the child objects. There is a check on objects to make sure they are loaded twice. This is implemented using a lazy loading logic.

C#
public static EntityObject LoadAllChild(this EntityObject source)
{
    List<propertyinfo> PropList = (from a in source.GetType().GetProperties()
                                   where a.PropertyType.Name == "EntityCollection`1"
                                   select a).ToList();
    foreach (PropertyInfo prop in PropList)
    {
        object instance = prop.GetValue(source, null);
        bool isLoad = 
          (bool)instance.GetType().GetProperty("IsLoaded").GetValue(instance, null);
        if (!isLoad)
        {
            MethodInfo mi = (from a in instance.GetType().GetMethods()
                             where a.Name == "Load" && a.GetParameters().Length == 0
                             select a).FirstOrDefault();

            mi.Invoke(instance, null);
        }
    }
    return (EntityObject)source;
}
ClearEntityReference

This method is used to clear the Entity Reference on the cloned Entity. The cloned Entity will be attached to the object only after the Entity References are cleared. The cloned object should be treated as new data and should create new Primary Keys and associate with Referential Integrity. Once the Entity Reference is cleared on the cloned object, the Framework will create temporary keys for associations (will treat this as a new Entity and follow the same logic). When data is moved to the database, the original keys are created and associated back to the EF model.

This method is again an extension of EntityObject. The bcheckHierarchy attribute is used to decide the logic needed to be applied only with the base object or required for all the child objects. Basically, there are two attributes: EntityKey and EntityReferences. EntityKey is set to null during this process. There is also a recursive call initiated to clear the Entity in the child objects.

C#
public static EntityObject ClearEntityReference(this EntityObject source, 
                                                bool bcheckHierarchy)
{
    return source.ClearEntityObject(bcheckHierarchy);
}

private static T ClearEntityObject<t>(this  T source, 
                 bool bcheckHierarchy) where T : class
{
    //Throw if passed object has nothing
    if (source == null) { throw new Exception("Null Object cannot be cloned"); }
    // get the TYpe of passed object 
    Type tObj = source.GetType();
    // check object Passed does not have entity key Attribute 
    if (tObj.GetProperty("EntityKey") != null)
    {
        tObj.GetProperty("EntityKey").SetValue(source, null, null);
    }

    //bcheckHierarchy this flag is used to check
    //and clear child object releation keys 
    if (!bcheckHierarchy)
    {
        return (T)source;
    }

    // Clearing the Entity for Child Objects 
    // Using the Linq get only Child Reference objects   from source object 
    List<propertyinfo> PropList = (from a in source.GetType().GetProperties()
               where a.PropertyType.Name.ToUpper() == "ENTITYCOLLECTION`1"
               select a).ToList();

    // Loop thorough List of Child Object and Clear the Entity Reference 
    foreach (PropertyInfo prop in PropList)
    {
        IEnumerable keys = 
          (IEnumerable)tObj.GetProperty(prop.Name).GetValue(source, null);
        
        foreach (object key in keys)
        {
            //Clearing Entity Reference from Parnet Object
            var ochildprop = (from a in key.GetType().GetProperties()
                              where a.PropertyType.Name == "EntityReference`1"
                              select a).SingleOrDefault();

            ochildprop.GetValue(key, null).ClearEntityObject(false);

            //Clearing the the Entity Reference from
            //Child object .This will recrusive action
            key.ClearEntityObject(true);
        }
    }
    return (T)source;
}

Sample implementation

The sample application attached has an Employee master table with two child tables and a grandchild table.

  • Employee (Level 1)
  • EmpAddress (Level 2)
  • EmpBasesal (Level 2)
  • EmpSalDetail (Level 3)

When the user wants to clone on employee data, he selects an employee and clones it. There are two options provided for the user: clone only the parent /base Entity, or clone parent, child, and grand-child.

The implementation does not have any restrictions to any depth/breath of the association level. It will support any level.

The sample has three level hierarchies:

C#
public ActionResult Clone(int id)
{
    //Load the Empbasic into Context
    var Emp = (from a in osamp.Employees
               where a.EMPID == id
               select a).FirstOrDefault();
    //Load EmpAddress using lazy load
    //  Emp.EmpAddresses.Load();
    //Call the extesnion Method Clone(cast is requried
    //as it was a Entity Object)
    var EMpnew =  Emp.Clone();

    //Clear Entity Values of New Object 
    EMpnew.ClearEntityReference(false );
    //detach the Load empbasic from Context
    osamp.Detach(Emp);
    //Add new Clone Object and save it DB 
    osamp.AddToEmployees((Employee)EMpnew );
    osamp.SaveChanges();
    osamp.AcceptAllChanges();
    return RedirectToAction("index"); ;
}

public ActionResult CloneChild(int id)
{
    var Emp = (from a in osamp.Employees
                where a.EMPID == id
                select a).FirstOrDefault();
                //Load EmpAddress using lazy load
                Emp.LoadAllChild();

    //Call the extesnion Method Clone(cast is requried
    //as it was a Entity Object)
    var Empnew =Emp.Clone();
    //Detach the real Object 
    osamp.Detach(Emp);
    //Clear the Entities in the Cloned object
    Empnew.ClearEntityReference(true);

    //Attach and save the clone in database
    osamp.AddToEmployees((Employee)Empnew);
    osamp.SaveChanges();

    return RedirectToAction("index");
}

System requirements

The code and sample will work on .NET Framework 4.0.

References

The following blogs helped to a great extent:

Conclusion

I wrote a blog on the same implementations with ADO.NET 3.5, but will work only to two levels of association and some methods have become obsolete. This code will have a few issues on .NET 3.5. This solution can be implemented by just including the CloneHelper class into a project. Hope this will help a few real time scenarios and increase developer productivity. Happy coding!

License

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