Deep Cloning/Copying of ADO.NET Entity Objects

in .NET Development, Uncategorized
This is my first post on urmanet.ch. Today I was experimenting with the ADO.NET Entity Framework and I was really surprised of the lack of support for cloning entity objects. After doing some research in the Internet I finally figured out that this feature might be added in the next release of .NET. However, this seems to take too long for me and so I tried to solve this problem by enhancing the EntityObject Class with an Extension Method. Extension methods are new in C# 3.0 and were mostly used for integrating LINQ in existing classes. In my opinion, enhancing the EntityObject with such an Extension Method seemed to be the best solution to add cloning functionalty. The code which is published here should work, but there are certainly things left and I would appreciate if someone gave me suggestions and feeback to improve the code below. Thank you in advance! Now, lets get into the code! For testing purposes, I created 3 database tables named Customers, Orders and OrderItems. These 3 tables are related togethter with foreign keys constraints and the ideas was, to deeply copy entities with all their related properties from top to the bottom (see picture below as example).
ADO.NET Entity Framework Model Example

ADO.NET Entity Framework Model Example

To test the code below, just create an ADO.NET Entity Model from an existing data source. The object context in this example is called TestEntities.

Used Namespaces

First, import these namespaces in your project:
  using System;  using System.Collections.Generic;  using System.Data;  using System.Data.Objects.DataClasses;  using System.Diagnostics;  using System.Linq;  using System.Reflection; 

Example of using the code in your project

This is an example of using the Clone() method in your code. The following example shows an iteration of Customers objects. Within the loop, the current Customer object makes a call to its Clone() member method which creates an exact copy of its caller object.The copy will be added tho the corresponding object context and at the end, all copied objects will be stored back in the datasource.
internal class Program
{
private static void Main(string[] args)
{
//Create an instance of the Object Context
var entities = new TestEntities();

//Get all customers from the datasource
var customers = from o in entities.Customers
select o;
//Iterate through the customers collection
foreach (var customer in customers)
{
//Create a deep copy of the customer object and add it to the Object Context
var myNewCustomer = (Customers) customer.Clone();
entities.AddToCustomers(myNewCustomer);
}

//Submit changes to the underlying datasource
entities.SaveChanges();
}
}

The Extension Method “Clone()” for the EntityObject Class

Everytime an (derived) object of the type “EntityObject” is accessed, the method “Clone()” will be available in the Intellisense of Visual Studio (see Screenshot).
Visual Studio Intellisense for Extension Methods

Visual Studio Intellisense Appearance for Extension Methods

Code Explanation The parameter “this EntityObject entityObject” of the Clone() method transports the object which was calling the method and not the base class like it may seems to be. After getting the calling class, some properties of the object must be filtered out, because they cannot be copied in the new object, e.g. EntityKey or EntityState. What we really want, is the content of the object, not its state variables or information, which is only used by the entity itself. After that, we need to know, if the property is a normal type like a string, integer, datetime etc. or a collection of related entities. In latter case, we need to call some functions by using reflection to get the original collection and – because this method works with infinite levels of depth – we make use of recursive calls. The new object now contains exactly the same values like the original object, excepting the key field, which will be empty or set to 0. This is necessary for adding the new object to the object context of the entiy model, otherwise we’ll get an exception error telling us, that the key already exists. The default key value is null – for string keys – or 0 – for numbers.

///
/// This class is used to store self references for
/// back tracking
///
public class SelfReferencesTracking
{
public string EntitySetName;
public EntityObject NewEntityObject;
public EntityKey OriginalKeys;
}

///
/// Extension method class for the EntityObject class
///
public static class EntityObjectExtension
{
//Enable tracking
private static readonly List _tracking =
new List();

///
/// These method makes a 1:1 copy of the original entity object
///
/// The original entity object /// The copied entity object
public static EntityObject Clone(this EntityObject entityObject)
{
//Get constructor for new object
var newEntityObject = entityObject.GetType().GetConstructor(
new Type[0]).Invoke(new object[0]);

_tracking.Add(new SelfReferencesTracking
{
EntitySetName = entityObject.EntityKey.EntitySetName,
OriginalKeys = entityObject.EntityKey,
NewEntityObject = (EntityObject)newEntityObject
});

//Copy all properties and its values of the given type
var properties = entityObject.GetType().GetProperties();
foreach (var property in properties)
{
try
{
var propertyValue = property.GetValue(entityObject, null);
PropertyInfo myProperty = property;
if (entityObject.EntityKey.EntityKeyValues.Where(x => x.Key == myProperty.Name).Count() == 0)
{
//Ignore all properties of these types
if (property.PropertyType != typeof(EntityKey) &&
property.PropertyType != typeof(EntityState) &&
property.PropertyType != typeof(EntityReference<>))
{
//Check, if the property is a complex type (collection), in that
//case, some special calls are necessary
if (property.GetCustomAttributes(
typeof(EdmRelationshipNavigationPropertyAttribute), false).Count() == 1)
{
//Check for self referencing entities
if (propertyValue.GetType() == entityObject.GetType())
{
//Get the self referenced entity object
var selfRefrencedEntityObject =
(EntityObject)property.GetValue(entityObject, null);

//This variable is used to store the new parent entity objects
EntityObject newParentEntityObject = null;

//This loops might be replaced by LINQ queries... I didn't try that
foreach (
var tracking in
_tracking.Where(
x =>
x.EntitySetName == selfRefrencedEntityObject.EntityKey.EntitySetName)
)
{
//Check, if the key is in the tracking list
foreach (
var newKeyValues in selfRefrencedEntityObject.EntityKey.EntityKeyValues)
{
//Iterate trough the keys and values
foreach (var orgKeyValues in tracking.OriginalKeys.EntityKeyValues)
{
//The key is stored in the tracking list, which means, this is
//the foreign key used by the self referencing property
if (newParentEntityObject == null)
{
if (orgKeyValues.Key == newKeyValues.Key &amp;amp;&amp;amp;
orgKeyValues.Value == newKeyValues.Value)
{
//Store the parent entity object
newParentEntityObject = tracking.NewEntityObject;
}
}
else
{
break;
}
}
}
}

//Set the value to the new parent entity object
property.SetValue(
newEntityObject,
newParentEntityObject, null);
}
else
{
//Entity collections are always generic
if (propertyValue.GetType().IsGenericType)
{
//Don't include self references collection, e.g. Orders1, Orders2 etc.
//Check for equality of the types (string comparison)
if (!propertyValue.GetType().GetGenericArguments().First().FullName.Equals(
entityObject.GetType().FullName))
{
//Get the entities of the given property
var entities =
(RelatedEnd)property.GetValue(entityObject, null);

//Load underlying collection, if not yet done...
if (!entities.IsLoaded) entities.Load();

//Create a generic instance of the entities collection object
var t = typeof(EntityCollection<>).MakeGenericType(
new[] { property.PropertyType.GetGenericArguments()[0] });

var newEntityCollection = Activator.CreateInstance(t);

//Iterate trough the entities collection
foreach (var entity in entities)
{
//Add the found entity to the dynamic generic collection
var addToCollection = newEntityCollection.GetType().GetMethod("Add");
addToCollection.Invoke(
newEntityCollection,
//new object[] {(EntityObject) entity});
new object[] { Clone((EntityObject)entity) });
}

//Set the property value
property.SetValue(
newEntityObject,
newEntityCollection,
null);
}
}
}

}
else
{
//Common task, just copy the simple type property into the new
//entity object
property.SetValue(
newEntityObject,
property.GetValue(entityObject, null), null);
}
}
}
}
catch (InvalidCastException ie)
{
//Hmm, something happend...
Debug.WriteLine(ie.Message);

continue;
}
catch (Exception ex)
{
//Hmm, something happend...
Debug.WriteLine(ex.Message);

continue;
}
}

return (EntityObject)newEntityObject;
}
}
 

Features

  • Support for copying entity objects from top to the bottom (copying of all children and their values)
  • Support for copying self-referenced entity objects (original relations will be tracked in an internal tracking list and copied entity objects will have the same relationship to the new parent entity object)
  • Simple access because it’s an extension methods, no parameters are required

Pending Tasks

As mentioned above, this code is the first try to implement Clone() functionaity into the ADO.NET Entity Framework. I tested it just with the example database model above (up to 3 Tables) for seeing, if it’s gonna work or not. Pending tasks are among others:
  • Support for bottom-up relationships like child entities containing foreign keys to other (upper) entities (not yet tested)

Conclusion

The published code should give you an idea of how to implement Cloning()-funcionality in the ADO.NET Entity Framework. If you have any suggestions, please feel free to post your comments here!
5 Comments

5 Comments

  1. zaengi

    HI,

    this is a great example, thanks a lot!

    Apparently however, your code won’t copy “simple references” to other entities, i.e. 1:1 relations.

    Assuming that customer has a new relation to a sales contact of type “person”, the cloned customer object will still have a null-reference to the person-entity.

    I’ve tried to figure out, how to do it, but wasn’t successful at all.

    How would you try?
    Would you deal with the auto-generated “reference properties” instead of the direct ones?
    (I mean, property “SalesPersonReference” instead of “SalesPerson” in this example)

    Thanks for any idea…

    Zaengi

  2. Thanks for the useful code.
    I tried with foreign key references: the entity itself gets ignored, and the reference property throws this error at line 155:
    “The EntityReference has already been initialized. InitializeRelatedReference should only be used to initialize a new EntityReference during deserialization of an entity object.”
    But I might have messed up converting to VB.
    An update to include these would be great!
    Guy

  3. Suggestion for handling foreign key references – replace line 153-157 with this:
    if (propertyValue.GetType.BaseType.Name == “EntityReference”) {
    //Simply copy the EntityKey to the new entity object’s EntityReference
    ((EntityReference)property.GetValue(newEntityObject, null)).EntityKey = ((EntityReference)property.GetValue(entityObject, null)).EntityKey;
    }
    else
    {
    //Common task, just copy the simple type property into the new entity object
    property.SetValue(
    newEntityObject,
    property.GetValue(entityObject, null), null);
    }

  4. Thanks to URMANET for the original code and also to GuyB for the suggestion on handling foreign key references. Worked well for me!

  5. Can you suggest how to clone the child objects associated with a parent entity ? My parent entity is cloned successfully but the children associated with it are not ?

Leave a Reply

Using Gravatars in the comments - get your own and be recognized!

XHTML: These are some of the tags you can use: <a href=""> <b> <blockquote> <code> <em> <i> <strike> <strong>