Answered by:
Deep cloning entity objects

Question
-
Hi,
I need to clone an entity object and the related objects slould also be cloned.
For example I clone a customer an it should also clone the customers orders and orderrecords.
I used http://www.urmanet.ch/?tag=c-30 as example but this code does not clone the orders and orderrecords (in the description it said it should, but is doesn't)
Does someone know how to fix the code of has some other example?
Thx.
Friday, December 11, 2009 11:51 AM
Answers
-
Hello,
Welcome to ADO.NET Entity Framework and LINQ to Entities forum!
The Clone method in the link that you provided is actually great. A few points to mention:
1. It only clones the related entities collection (EntityCollection<T>) but not the parent entity (EntityObject). E.g.
================================================================
var p = context.ParentTable.Include("ChildTable").First();var p2 = p.Clone();
foreach (var c in ((ParentTable)p2).ChildTable)
{
Console.WriteLine(c.id);
}
================================================================Here the ChildTable is an EntityCollection<ChildTable>. The codes will fine. We can only deeply clone the entity in one way, so the parent table navigation property is not included, otherwise, the search loop will become an infinite loop.
2. The Clone method also handles the self reference entities.
3. The Entity Key of the cloned entity is empty or 0.
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.- Marked as answer by Michael Sun [MSFT]Microsoft employee Friday, December 18, 2009 12:38 AM
Tuesday, December 15, 2009 6:31 AM -
I have modified the format and fix some small issues in the sample codes:
========================================================================
////// This class is used to store self references for
/// back tracking
///
public class SelfReferencesTracking
{
public string EntitySetName;
public EntityObject NewEntityObject;
public EntityKey OriginalKeys;
}
///http://www.urmanet.ch/?tag=c-30
///
/// Extension method class for the EntityObject class
///
public static class EntityObjectExtension
{
//Enable tracking
private static readonly List<SelfReferencesTracking> _tracking = new List<SelfReferencesTracking>();
///
/// 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 &&
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;
}
}
========================================================================Best Regards,
Lingzhi Sun
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.- Marked as answer by Michael Sun [MSFT]Microsoft employee Friday, December 18, 2009 12:38 AM
Tuesday, December 15, 2009 6:50 AM
All replies
-
Hello,
Welcome to ADO.NET Entity Framework and LINQ to Entities forum!
The Clone method in the link that you provided is actually great. A few points to mention:
1. It only clones the related entities collection (EntityCollection<T>) but not the parent entity (EntityObject). E.g.
================================================================
var p = context.ParentTable.Include("ChildTable").First();var p2 = p.Clone();
foreach (var c in ((ParentTable)p2).ChildTable)
{
Console.WriteLine(c.id);
}
================================================================Here the ChildTable is an EntityCollection<ChildTable>. The codes will fine. We can only deeply clone the entity in one way, so the parent table navigation property is not included, otherwise, the search loop will become an infinite loop.
2. The Clone method also handles the self reference entities.
3. The Entity Key of the cloned entity is empty or 0.
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.- Marked as answer by Michael Sun [MSFT]Microsoft employee Friday, December 18, 2009 12:38 AM
Tuesday, December 15, 2009 6:31 AM -
I have modified the format and fix some small issues in the sample codes:
========================================================================
////// This class is used to store self references for
/// back tracking
///
public class SelfReferencesTracking
{
public string EntitySetName;
public EntityObject NewEntityObject;
public EntityKey OriginalKeys;
}
///http://www.urmanet.ch/?tag=c-30
///
/// Extension method class for the EntityObject class
///
public static class EntityObjectExtension
{
//Enable tracking
private static readonly List<SelfReferencesTracking> _tracking = new List<SelfReferencesTracking>();
///
/// 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 &&
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;
}
}
========================================================================Best Regards,
Lingzhi Sun
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.- Marked as answer by Michael Sun [MSFT]Microsoft employee Friday, December 18, 2009 12:38 AM
Tuesday, December 15, 2009 6:50 AM -
I am able to clone my entities using above pointed article but I am not able to clone the child objects associated with it. Can you suggest some other approach so that while creating clones, all the child/associated entities are also cloned. Thanks!Thursday, March 10, 2011 12:48 PM
-
Does the clone method handle one-to-one referenced entities?Monday, May 2, 2011 11:35 PM
-
Hello,
I suspect you do not neccessarily need a deep clone - a new object the with the properties copied is usually enough - that way if a property is re-assigned it will not mess with the EntityObject you cloned.
From: http://www.codeproject.com/Tips/474296/Clone-an-Entity-in-Entity-Framework-4, here is how:
public static T CopyEntity<T>(MyContext ctx, T entity, bool copyKeys = false) where T : EntityObject { T clone = ctx.CreateObject<T>(); PropertyInfo[] pis = entity.GetType().GetProperties(); foreach (PropertyInfo pi in pis) { EdmScalarPropertyAttribute[] attrs = (EdmScalarPropertyAttribute[])pi.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false); foreach (EdmScalarPropertyAttribute attr in attrs) { if (!copyKeys && attr.EntityKeyProperty) continue; pi.SetValue(clone, pi.GetValue(entity, null), null); } } return clone; }
You can copy related entites to your cloned object now too; say you had an entity: Customer, which had the Navigation Property: Orders. You could then copy the Customer and their Orders using the above method by:
Customer newCustomer = CopyEntity(myObjectContext, myCustomer, false); foreach(Order order in myCustomer.Orders) { Order newOrder = CopyEntity(myObjectContext, order, true); newCustomer.Orders.Add(newOrder); }
- Proposed as answer by markmnl Thursday, October 11, 2012 1:26 AM
Thursday, October 11, 2012 12:37 AM