locked
Deep cloning entity objects RRS feed

  • 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.

    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.
    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.

    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.
    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