New Posts New Posts RSS Feed: Deep Clone
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

Deep Clone

 Post Reply Post Reply Page  12>
Author
hueha View Drop Down
Newbie
Newbie


Joined: 23-Jul-2010
Posts: 38
Post Options Post Options   Quote hueha Quote  Post ReplyReply Direct Link To This Post Topic: Deep Clone
    Posted: 17-Jul-2011 at 3:14am
the interface is simple

public interface IMyCloningMethods
    {
        void GetEntityGraphAsync(Action<IList<Object>> callback);
       
    }

What you do is for each object you use findentitygraph to return a list of related objects to the depth you require.



Back to Top
stevef View Drop Down
Newbie
Newbie
Avatar

Joined: 15-Jul-2011
Location: NY
Posts: 9
Post Options Post Options   Quote stevef Quote  Post ReplyReply Direct Link To This Post Posted: 15-Jul-2011 at 12:13pm
I realize this is an old thread, last msg sent being from a year ago.  But I sure could use this functionality.
I'm surprised this is not already provided by DevForce.  I realize the complexity of deep-cloning the graph.  But as Ward suggested, this indeed can be handled via metadata (ala the buddy classes that can be created to implement verification), to indicate what is clonable and what is not.
Alternately, why not provide a deep-clone method, with a depth parameter?
 
In lieu of using something from IB, I would like to use hueha's suggested implementation, but the code posted here is missing definition of IMyCloningMethods, and I'm at a loss when it comes to inferring its implementation.
Any chance of posting it, hueha?
I'd be much obliged.
 
Thanks,
Back to Top
hueha View Drop Down
Newbie
Newbie


Joined: 23-Jul-2010
Posts: 38
Post Options Post Options   Quote hueha Quote  Post ReplyReply Direct Link To This Post Posted: 09-Nov-2010 at 4:20am
ok rewritten the two routines that use reflection to use the entitymetadata.  It's whole lot simpler and easier to work with

private static void FixForeignKeys(Entity clone, Dictionary<EntityKey, Entity> clonedItems)
        {

            EntityMetadata cloneEntityMetadata = EntityMetadataStore.Instance.GetEntityMetadata(clone.GetType());

            foreach (var prop in cloneEntityMetadata.ScalarNavigationProperties)
            {
                EntityKey key =((Entity) prop.GetValue(clone)).EntityAspect.EntityKey ;

                if (clonedItems.ContainsKey(key))
                {
                    var newRelatedItem = clonedItems[key];
                    prop.SetValue(clone, newRelatedItem);
                }
            }
        }

        private static void RelinkRelations(Entity objectToClone, Entity clone, Dictionary<EntityKey, Entity> clonedItems)
        {
              EntityMetadata cloneEntityMetadata = EntityMetadataStore.Instance.GetEntityMetadata(clone.GetType());

              foreach (var listNavigationProperty in cloneEntityMetadata.ListNavigationProperties)
              {
                    IList listToClone = (IList)listNavigationProperty.GetValue(objectToClone);
                    IList listOfClone = (IList)listNavigationProperty.GetValue(clone);
                    //Need an umboundList because the list could be updated by the addition of new clones e.g. in many to many relationships
                    List<Entity> unboundListToClone = new List<Entity>();
                    foreach (var listItem in listToClone)
                    {

                        if (listItem is Entity)
                        {
                            unboundListToClone.Add((Entity)listItem);
                        }
                    }
                    foreach (var listItem in unboundListToClone)
                    {
                        Entity relatedObject;
                        if (clonedItems.ContainsKey(listItem.EntityAspect.EntityKey))
                        {
                            relatedObject = clonedItems[listItem.EntityAspect.EntityKey];
                        }
                        else
                        {
                            relatedObject = listItem;
                        }
                        listOfClone.Add(relatedObject);
                    } 
              }

Back to Top
hueha View Drop Down
Newbie
Newbie


Joined: 23-Jul-2010
Posts: 38
Post Options Post Options   Quote hueha Quote  Post ReplyReply Direct Link To This Post Posted: 09-Nov-2010 at 2:16am
public static void DeepClone(Entity objectToClone, JTS2Entities mgr, Action<Entity> callBack)
        {
            Dictionary<EntityKey, Entity> clonedItems = new Dictionary<EntityKey, Entity>();
            DeepClone( objectToClone, mgr, clonedItems, callBack);
        }
        private static void DeepClone(Entity objectToClone, JTS2Entities mgr, Dictionary<EntityKey, Entity> clonedItems, Action<Entity> callBack)
        {
            ((IMyCloningMethods) objectToClone).GetEntityGraphAsync((list) => 
            {
              //set up clones
              foreach (Entity relatedItem in list)
              {
                  Entity relatedClone=(Entity) ((ICloneable) relatedItem).Clone();
                  MakeUnique((IMyCloningMethods) relatedClone);
                  mgr.AddEntity(relatedClone);
                  clonedItems.Add(relatedItem.EntityAspect.EntityKey, relatedClone);
              }
              //fix up IDs
              foreach (Entity relatedItem in list)
              {
                  FixForeignKeys(clonedItems[relatedItem.EntityAspect.EntityKey], clonedItems);
              }
              foreach (Entity relatedItem in list)
              {
                  RelinkRelations(relatedItem, clonedItems[relatedItem.EntityAspect.EntityKey], clonedItems);
              }
              callBack(clonedItems[objectToClone.EntityAspect.EntityKey]);
            });
        }
private static void FixForeignKeys(Entity clone, Dictionary<EntityKey, Entity> clonedItems)
        {
            var props = clone.GetType().GetProperties();
            
            foreach (var prop in props)
            {
                var attr = prop.GetCustomAttributes(typeof(RelationPropertyAttribute), true);
                if (attr.Length > 0)
                {
                    if (((RelationPropertyAttribute)attr[0]).QueryDirection == QueryDirection.ToRole2)
                    {
                        if (!IsSubclassOfRawGeneric(typeof(RelatedEntityList<>), prop.PropertyType))
                        {
                            EntityKey key = ((Entity)prop.GetGetMethod().Invoke(clone, null)).EntityAspect.EntityKey;
                            if (clonedItems.ContainsKey(key))
                            {
                                var newRelatedItem = clonedItems[key];
                                prop.GetSetMethod().Invoke(clone, new object[] { newRelatedItem });
                            }
                        }
                    }
                }
            }
        }

        private static void RelinkRelations(Entity objectToClone, Entity clone, Dictionary<EntityKey, Entity> clonedItems)
        {
            var props = objectToClone.GetType().GetProperties();
            foreach (var prop in props)
            {
                if (IsSubclassOfRawGeneric(typeof(RelatedEntityList<>), prop.PropertyType))
                {
                    IList listToClone = (IList)prop.GetGetMethod().Invoke(objectToClone, null);
                    IList listOfClone = (IList)prop.GetGetMethod().Invoke(clone, null);
                    listOfClone.Clear();
                    if (listToClone != null)
                    {
                        //Need an umboundList because the list could be updated by the addition of new clones e.g. in many to many relationships
                        List<Entity> unboundListToClone = new List<Entity>();
                        foreach (var listItem in listToClone)
                        {

                            if (listItem is Entity)
                            {
                                unboundListToClone.Add((Entity)listItem);
                            }
                        }
                        foreach (var listItem in unboundListToClone)
                        {
                            Entity relatedObject;
                            if (clonedItems.ContainsKey(listItem.EntityAspect.EntityKey))
                            {
                                relatedObject = clonedItems[listItem.EntityAspect.EntityKey];
                            }
                            else
                            {
                                relatedObject = listItem;
                            }
                            listOfClone.Add(relatedObject);
                        }
                    }
                }
            }
        }
        static bool IsSubclassOfRawGeneric(Type generic, Type toCheck)
        {
            while (toCheck != typeof(object))
            {
                var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
                if (generic == cur)
                {
                    return true;
                }
                toCheck = toCheck.BaseType;
            }
            return false;
        }
        static private void MakeUnique(IMyCloningMethods entity)
        {
            foreach (var propName in entity.FieldsWithUniqueConstraint) //this is a list of field names (string) that I maintain manually in code
            {
                var prop = entity.GetType().GetProperty(propName);
                //make name unique then set it back to the property
                object[] parameters = {prop.GetGetMethod().Invoke(entity, null) + " copied on " + DateTime.Now.ToString("dd-MMM-yyyy hh:mm:ss")};
                prop.GetSetMethod().Invoke(entity, parameters);
            }
        }

Back to Top
hueha View Drop Down
Newbie
Newbie


Joined: 23-Jul-2010
Posts: 38
Post Options Post Options   Quote hueha Quote  Post ReplyReply Direct Link To This Post Posted: 09-Nov-2010 at 2:12am
Yeah I eventually came to the same conclusion.  To get the list of objects to clone I now use have a method on all my entities that uses EntityAspect.EntityManager.FindEntityGraph to return the objects I want to clone.  Then I clone all the items and keep a mapping table to map clones to the original object.  I need to do this in order to remap all the related objects to the corresponding clones.  I used reflection to get to your property attributes.  I'll post my code in the next post.  Do you think I should rewrite using the GetEntityMetadata?
Back to Top
WardBell View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 31-Mar-2009
Location: Emeryville, CA,
Posts: 338
Post Options Post Options   Quote WardBell Quote  Post ReplyReply Direct Link To This Post Posted: 08-Nov-2010 at 8:29pm
You may not need reflection.
 
DevForce maintains a MetadataStore with a wealth of information about entities and their relationships. You can get the metadata for an entity type via

   IdeaBlade.EntityModel.EntityMetadataStore.Instance.GetEntityMetadata(someType)

The method takes a type (rather than a generic type parameter) as you can see so it should be easy for you to assemble the pieces you need for any arbitrary type to get a complete vision of the graph that you will need before making any async calls.
 
You can use Coroutine.Parallel to construct a series of parallel async calls to retrieve the related entities and their related entities ... up to the depth of the clone you need.
 
You probably don't want clone every related entity. For example, if a Customer has a navigation to a CustomerCode entity, you probably don't clone the code. There is nothing in the graph structure per se that tells you what to clone and what not to clone. Nothing in the graph structure tells you how deep to clone ... how far to go along the navigation paths. I don't know how you can make any of these decisions a priori; you have to bring some business knowledge to the table ... perhaps in the form of your own metadata (which would also know which values must be unique and how to make them unique ... information not available to us from EF).
 
These are some of the reasons that we don't give you an automated deep clone facility.
 
I can imagine coupling your own metadata to some generalized cloning machinery. When you figure this out, you might choose to perform all of your cloning in a separate Entity Manager to isolate that activity from everything in your main manager.
 
Obviously this is more involved than any of us would like. It's not a scenario we've run into often.
Back to Top
hueha View Drop Down
Newbie
Newbie


Joined: 23-Jul-2010
Posts: 38
Post Options Post Options   Quote hueha Quote  Post ReplyReply Direct Link To This Post Posted: 16-Oct-2010 at 6:04am
1) I was afraid you were gonna say that.  I guess I'll have to keep a list myself of any unique columns belonging to that object
2) The problem is I'm trying to do it generically using reflection so there could be multiple steps needing to be resolved.  I suppose I could jump into the clone method recursively and asynchronously but the concept is starting to cause a stack overflow in my mind :D.  I'll give it a go
Back to Top
sbelini View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 13-Aug-2010
Location: Oakland
Posts: 786
Post Options Post Options   Quote sbelini Quote  Post ReplyReply Direct Link To This Post Posted: 12-Oct-2010 at 9:48am
Hi,
 
1) Unfortunately you will not be able to know if a field is a unique index (if not part of the PK)
 
2) since it's async, you must wait until it's resolved:
 
  var orders = aCustomer.Orders;
  foreach (Order order in orders) {
    if (order.EntityAspect.IsPendingEntity) {
      order.EntityAspect.PendingEntityResolved += new EventHandler<PendingEntityResolvedEventArgs>(PendingEntityResolved);
    }
  }
  .
  .
  .
 
 
  void PendingEntityResolved(object sender, PendingEntityResolvedEventArgs e) {
    // some code
  }
 
Back to Top
hueha View Drop Down
Newbie
Newbie


Joined: 23-Jul-2010
Posts: 38
Post Options Post Options   Quote hueha Quote  Post ReplyReply Direct Link To This Post Posted: 06-Oct-2010 at 3:00pm
sorry I should have been more specific
1) The unique index is not part of the primary key. 
2) I can tell they are pending but how do I force the entire entity graph to load.  I can't use a query to put them in cache because it is a generic function and all I know is that it is an entity object which might or might not have a relatedentitylist

Back to Top
sbelini View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 13-Aug-2010
Location: Oakland
Posts: 786
Post Options Post Options   Quote sbelini Quote  Post ReplyReply Direct Link To This Post Posted: 06-Oct-2010 at 10:40am
hueha,
 
You can use
 
anEntity.EntityAspect.EntityMetadata.KeyProperties;
 
to find out the entity key.
 
You can also find if an entity is a pending entity with
 
anEntity.EntityAspect.IsPendingEntity;
 
 
Back to Top
hueha View Drop Down
Newbie
Newbie


Joined: 23-Jul-2010
Posts: 38
Post Options Post Options   Quote hueha Quote  Post ReplyReply Direct Link To This Post Posted: 06-Oct-2010 at 7:31am
2) Another issue is how do I ensure none of the relatedentitylists are pending?
Back to Top
hueha View Drop Down
Newbie
Newbie


Joined: 23-Jul-2010
Posts: 38
Post Options Post Options   Quote hueha Quote  Post ReplyReply Direct Link To This Post Posted: 06-Oct-2010 at 7:04am
I've implemented what I outlined above and it works more or less (I'll post code when I'm done) but I have the following issue

1) I have a unique index on a field (companyName) and when I clone the entity and save it I get a violation of unique constraint from the database.  Is there a way of knowing what unique indices/constraints are in a model?  If so I could append a string to ensure it is unique before saving the clone.


Back to Top
hueha View Drop Down
Newbie
Newbie


Joined: 23-Jul-2010
Posts: 38
Post Options Post Options   Quote hueha Quote  Post ReplyReply Direct Link To This Post Posted: 23-Sep-2010 at 5:57am
I'll try and write a generic deep clone function that uses reflection.  Right now I'm thinking

1) Create clone
2) Reflect off properties and find any relatedentitylists
3) For each item in each RelatedEntityList
          recursively call deep clone with item
          set the property that is the same name as this class name to the clone


there are a couple of issues I can think of
1) I have a many to many relationship which could cause the recursion to loop infinitely.  Maybe I could keep a track of original entity objects I have already cloned.
2) I'm assuming that any relationships are cleared from the clone.  

For example lets say I have 
a company table with a companyid
        a location table with locationid, foreign key companyid
        a contact table with contactid, foreign keys companyid, locationid

If I clone a contact and I set the locationid to the cloned location then the companyid should also point to the cloned company not the original one
Back to Top
sbelini View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 13-Aug-2010
Location: Oakland
Posts: 786
Post Options Post Options   Quote sbelini Quote  Post ReplyReply Direct Link To This Post Posted: 22-Sep-2010 at 11:54am
In order to do a deep clone, you will need to implement it.
i.e. cloning an employee and his orders:
 
  Employee newEmployee = (Employee)((ICloneable)anEmployee).Clone();
  mgr.GenerateId(newEmployee, Employee.PropertyMetadata.EmployeeID);
  mgr.AddEntity(newEmployee);
  IList<Order> orders = anEmployee.Orders.ToList();
  foreach (Order ord in orders) {
    Order newOrder = (Order)((ICloneable)ord).Clone();
    mgr.GenerateId(newOrder, Order.PropertyMetadata.OrderID);
    newOrder.Employee = newEmployee;
    mgr.AddEntity(newOrder);
  }
 
 
Back to Top
hueha View Drop Down
Newbie
Newbie


Joined: 23-Jul-2010
Posts: 38
Post Options Post Options   Quote hueha Quote  Post ReplyReply Direct Link To This Post Posted: 22-Sep-2010 at 6:51am
Originally posted by davidklitzke

The defect has been fixed.  The 6.0.5 Release Notes say:

•    The ICloneable.Clone() implementation on Entity previously caused the cloned entity to share its fields with the original entity. Fixed. [B1545]


That's good news.  So any ideas on doing a deep clone?
Back to Top
davidklitzke View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 14-Jun-2007
Posts: 715
Post Options Post Options   Quote davidklitzke Quote  Post ReplyReply Direct Link To This Post Posted: 20-Sep-2010 at 9:17am
The defect has been fixed.  The 6.0.5 Release Notes say:

•    The ICloneable.Clone() implementation on Entity previously caused the cloned entity to share its fields with the original entity. Fixed. [B1545]


Back to Top
 Post Reply Post Reply Page  12>

Forum Jump Forum Permissions View Drop Down