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.
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.
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.
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.
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
{
if (source == null) { throw new Exception("Null Object cannot be cloned"); }
Type tObj = source.GetType();
if (tObj.GetProperty("EntityKey") != null)
{
tObj.GetProperty("EntityKey").SetValue(source, null, null);
}
if (!bcheckHierarchy)
{
return (T)source;
}
List<propertyinfo> PropList = (from a in source.GetType().GetProperties()
where a.PropertyType.Name.ToUpper() == "ENTITYCOLLECTION`1"
select a).ToList();
foreach (PropertyInfo prop in PropList)
{
IEnumerable keys =
(IEnumerable)tObj.GetProperty(prop.Name).GetValue(source, null);
foreach (object key in keys)
{
var ochildprop = (from a in key.GetType().GetProperties()
where a.PropertyType.Name == "EntityReference`1"
select a).SingleOrDefault();
ochildprop.GetValue(key, null).ClearEntityObject(false);
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:
public ActionResult Clone(int id)
{
var Emp = (from a in osamp.Employees
where a.EMPID == id
select a).FirstOrDefault();
var EMpnew = Emp.Clone();
EMpnew.ClearEntityReference(false );
osamp.Detach(Emp);
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();
Emp.LoadAllChild();
var Empnew =Emp.Clone();
osamp.Detach(Emp);
Empnew.ClearEntityReference(true);
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!