EntityGraph Clone() & IsCloneOf()

Developer
Jan 28, 2011 at 4:05 PM
Edited Jan 28, 2011 at 4:08 PM

here's my scenario:

                var orig = myDomainContext.Tickets.ElementAt(0);
                var graph = orig.EntityGraph(orig.EntityGraphShape);

                var clone = graph.Clone(); 
                var cloneGraph = clone.EntityGraph(clone.EntityGraphShape);

so I've cloned the original Ticket, then created a graph of the clone.

Now I want to know if cloneGraph.IsCloneOf(graph) ... but it's false.

??

Developer
Jan 28, 2011 at 4:24 PM

Actually, the previous question might be a bit premature.

Why does Clone() result in an entity with a different [Key] than the entity that it was cloned from?

Developer
Jan 28, 2011 at 4:37 PM

Is there really any reason why I shouldn't simply: 

              clone.ApplyState(
                  orig.ExtractState(ExtractType.OriginalState),
                  orig.ExtractState(ExtractType.ModifiedState));

 or bypass the Clone() completely:

              var manualClone = new Ticket();
              manualClone.ApplyState( /* same as above */)

 

Developer
Jan 28, 2011 at 4:40 PM
Edited Jan 28, 2011 at 5:52 PM

oh.

ApplyState() won't take care of associations.

So I'm back to my original question (that I didn't post):

How should I go about updating a stale graph with an updated graph?

 

[Edit]

If I do (I added a new Note after loading and before cloning) :

clone.ApplyState(
             ticket.ExtractState(RiaServicesContrib.ExtractType.OriginalState), 
             ticket.ExtractState(RiaServicesContrib.ExtractType.ModifiedState))
clone.LogNotes.ElementAt(0).ApplyState(
             ticket.Notes.ElementAt(0).ExtractState(RiaServicesContrib.ExtractType.OriginalState),
             ticket.Notes.ElementAt(0).ExtractState(RiaServicesContrib.ExtractType.ModifiedState))

It gets me where I need to be (in this explicit example: all DataMembers are now set like the original), but I would assume Clone() should be doing this?


(And this doesn't consider elements being removed during an edit of the original. for a new clone of a non-changed entity this wouldn't matter, obviously. But it will after I clone the entity into a new context, make changes, then update the stale entity in the original context. Detaching the stale entity and adding an updated clone results in lists bound to that data not showing newly attached items)) 

[/Edit]

Developer
Jan 28, 2011 at 5:51 PM

I suppose this is the new stuff I was going to have to write to extend the SubmitSingleChanges ... =) 

What I should probably do is create a new method with the same signature as:

        private TClone CloneDataMembers<TClone>(TClone entity) where TClone : Entity


and use it with the GraphMap(...) method to do the nested ApplyState?

 

On a side note, is there a plan to finalize the GraphChangeSet feature? (handful of NotImplementedExceptions being thrown)

Coordinator
Jan 28, 2011 at 6:27 PM
Edited Jan 28, 2011 at 10:11 PM

To both of you, here is the new version of RiaServicesExtensions.cs that I am working on. I am putting out a call for more help getting unit tests written and I will get it checked in for real soon. Two parts of it you may find interesting is the new clone features that do direct copies of entities without using a dictionary in the middle and the IsDuplicate method that can tell if an entity is a clone of another entity.

[Code has now been checked in.]

Developer
Jan 28, 2011 at 6:54 PM
joebrockhaus wrote:

Actually, the previous question might be a bit premature.

Why does Clone() result in an entity with a different [Key] than the entity that it was cloned from?

If the cloned entity would have the same key, it would be identical to the origional one (i.e. it would be the same object).

You wouldn't be able to e.g. store the cloned entity in the database.

The crucial thing of cloning is that everything is duplicated except for [key] properties.

 

 

 

Developer
Jan 28, 2011 at 7:01 PM
joebrockhaus wrote:

here's my scenario:

                var orig = myDomainContext.Tickets.ElementAt(0);
                var graph = orig.EntityGraph(orig.EntityGraphShape);

                var clone = graph.Clone(); 
                var cloneGraph = clone.EntityGraph(clone.EntityGraphShape);

so I've cloned the original Ticket, then created a graph of the clone.

Now I want to know if cloneGraph.IsCloneOf(graph) ... but it's false.

??

I'm not sure what's going wrong here. What does 'orig.EntityGraphShape' and 'clone.EntityGraphShape' do? Did you check that they're equal?

Why do you create yet another entitygraph from clone? graph.Clone() already gives you an entity graph. No need to call clone.EntityGraph.

Developer
Jan 28, 2011 at 7:07 PM
joebrockhaus wrote:

oh.

ApplyState() won't take care of associations.

So I'm back to my original question (that I didn't post):

How should I go about updating a stale graph with an updated graph?

 

[Edit]

If I do (I added a new Note after loading and before cloning) :

 

clone.ApplyState(
             ticket.ExtractState(RiaServicesContrib.ExtractType.OriginalState), 
             ticket.ExtractState(RiaServicesContrib.ExtractType.ModifiedState))
clone.LogNotes.ElementAt(0).ApplyState(
             ticket.Notes.ElementAt(0).ExtractState(RiaServicesContrib.ExtractType.OriginalState),
             ticket.Notes.ElementAt(0).ExtractState(RiaServicesContrib.ExtractType.ModifiedState))

 

It gets me where I need to be (in this explicit example: all DataMembers are now set like the original), but I would assume Clone() should be doing this?


(And this doesn't consider elements being removed during an edit of the original. for a new clone of a non-changed entity this wouldn't matter, obviously. But it will after I clone the entity into a new context, make changes, then update the stale entity in the original context. Detaching the stale entity and adding an updated clone results in lists bound to that data not showing newly attached items)) 

[/Edit]

The clone operation of entity graph doesn't take state of entities into account. Actually, I'm waiting for the new entity clone operation of Colin. Currently, the clone method simply copies the property values of the original entity to the clone entity.

If you look at the implementation of the Clone method of Entitygraph you see that it makes use of a more generic GraphMap method. This is a structure-preserving operation that you can use for all kinds of graph opererations (cloning is just an example). So, if cloning is not exactly what you need, you can still benefit from the GraphMap method. If you find a way to improve the implementation of Clone, you're more than welcome to contribute. You can check yourself that the implementation is really very simple!

Developer
Jan 28, 2011 at 7:09 PM

Colin - thanks!

Merijn - I don't follow why it wouldn't be able get saved to the database: Won't it be seen as an update?
            That being said, I suppose I could see a need to copy the entity AND give all entities new ID's.
            But I think that's a rather complicated procedure: You have to be able to provide or generate all the new ID's that will be needed for the associations, when fleshing out the cloned graph, right? 

Developer
Jan 28, 2011 at 7:13 PM
joebrockhaus wrote:

I suppose this is the new stuff I was going to have to write to extend the SubmitSingleChanges ... =) 

What I should probably do is create a new method with the same signature as:

 

        private TClone CloneDataMembers<TClone>(TClone entity) where TClone : Entity

 


and use it with the GraphMap(...) method to do the nested ApplyState?

 

On a side note, is there a plan to finalize the GraphChangeSet feature? (handful of NotImplementedExceptions being thrown)

Exactly, I just proposed this, but you figured it out yourself!

 

Good point! Yes, GraphChangeSet needs to be finalized. I forgot about the exceptions.

Developer
Jan 28, 2011 at 7:21 PM
joebrockhaus wrote:

Colin - thanks!

Merijn - I don't follow why it wouldn't be able get saved to the database: Won't it be seen as an update?
            That being said, I suppose I could see a need to copy the entity AND give all entities new ID's.
            But I think that's a rather complicated procedure: You have to be able to provide or generate all the new ID's that will be needed for the associations, when fleshing out the cloned graph, right? 

It depends on how you deal with the clone.

If you make a clone of a graph of entities that is in a domain context, then add this clone to the domain context you would get an error stating that an entity with the same key already exists.

If you would add the clone to an object context where the entities are not yet present, you're right. Then you would not use 'add' but 'attach' and the database would see it as updates.

Maybe it would actually a good feature to also have a version of clone that copies Keys as well (but it may be tricky).

As indicated, the current implementation uses new ID's. The GraphMap does all the work need for maintaining/updating the associations.

Developer
Jan 28, 2011 at 7:22 PM
MdeJ wrote:

I'm not sure what's going wrong here. What does 'orig.EntityGraphShape' and 'clone.EntityGraphShape' do? Did you check that they're equal?

Why do you create yet another entitygraph from clone? graph.Clone() already gives you an entity graph. No need to call clone.EntityGraph.

graph.Clone() gets me a new instance of the Ticket class. (with an inherent graph, via associations)

EntityGraphShape is a property on the Ticket entity, which is the same for all Ticket instances: [ return new EntityGraphShape().Edge<Ticket, Note>(ticket => ticket.Notes); ]

I was originally curious about IsCloneOf() because I thought I might be able to use it to compare cloned, and potentially modified, instances across isolated DomainContexts. But I only wanted to because the [Key]s were not being copied into the clones as I expected.

Developer
Jan 28, 2011 at 7:33 PM
joebrockhaus wrote:
MdeJ wrote:

I'm not sure what's going wrong here. What does 'orig.EntityGraphShape' and 'clone.EntityGraphShape' do? Did you check that they're equal?

Why do you create yet another entitygraph from clone? graph.Clone() already gives you an entity graph. No need to call clone.EntityGraph.

graph.Clone() gets me a new instance of the Ticket class. (with an inherent graph, via associations)

EntityGraphShape is a property on the Ticket entity, which is the same for all Ticket instances: [ return new EntityGraphShape().Edge<Ticket, Note>(ticket => ticket.Notes); ]

I was originally curious about IsCloneOf() because I thought I might be able to use it to compare cloned, and potentially modified, instances across isolated DomainContexts. But I only wanted to because the [Key]s were not being copied into the clones as I expected.

You're right. It is a while ago that I used this stuff. I was confused because I'm used to call clone on an entity and not on an entity graph e.g. I would write 'var clone =orig.Clone(orig,EntityGraphShape);

If you can send me your code I can check what's going wrong.

 

Developer
Jan 28, 2011 at 7:41 PM
Edited Jan 28, 2011 at 7:43 PM
MdeJ wrote:

It depends on how you deal with the clone.

If you make a clone of a graph of entities that is in a domain context, then add this clone to the domain context you would get an error stating that an entity with the same key already exists.

If you would add the clone to an object context where the entities are not yet present, you're right. Then you would not use 'add' but 'attach' and the database would see it as updates.

Maybe it would actually a good feature to also have a version of clone that copies Keys as well (but it may be tricky).

As indicated, the current implementation uses new ID's. The GraphMap does all the work need for maintaining/updating the associations.

(by order of bold)

  • that makes sense. in my case we currently don't have a need to make copies of entities like this.
  • oh yah - good point. I think when I've said add, i've meant attach :) 
    • the process is currently:
      cloned = MasterContext.Tickets.Single(..).CloneExact();
      IsolatedContext.Tickets.Attach(cloned);
      // user makes some changes
      IsolatedContext.SubmitChanges();
      // I now need to update the original entity in the MasterContext 
      // -- this is because the MasterContext is where the users will be performing searches, viewing lists of tickets, etc. 
      //     if they open a ticket to edit, make changes, and those changes aren't reflected in the list where they edited the ticket from, it's a bug.
      //    - similarly, I could avoid all of this trouble by simply detaching it from the MasterContext and attaching it to the isolated one. 
      //      But this is problematic also, because then the item disappears from their list, and Attaching it back to the MasterContext will not result in it showing up in the list again.
      //      - to further complicate this, the list is sorted, paged, and filtered against the database (using DomainCollectionView)
      //    - I could perhaps not detach or copy the entity into the isolated context, but instead load it (to isolated) from the database, but I still have the problem of then updating the Master Context. 
  • Does this mean that you don't think the nested ApplyState will work?
Developer
Jan 28, 2011 at 7:47 PM
MdeJ wrote:

You're right. It is a while ago that I used this stuff. I was confused because I'm used to call clone on an entity and not on an entity graph e.g. I would write 'var clone =orig.Clone(orig,EntityGraphShape);

If you can send me your code I can check what's going wrong.

I didn't even know that existed! :-x

Developer
Jan 28, 2011 at 8:03 PM
joebrockhaus wrote:
MdeJ wrote:

It depends on how you deal with the clone.

If you make a clone of a graph of entities that is in a domain context, then add this clone to the domain context you would get an error stating that an entity with the same key already exists.

If you would add the clone to an object context where the entities are not yet present, you're right. Then you would not use 'add' but 'attach' and the database would see it as updates.

Maybe it would actually a good feature to also have a version of clone that copies Keys as well (but it may be tricky).

As indicated, the current implementation uses new ID's. The GraphMap does all the work need for maintaining/updating the associations.

(by order of bold)

  • that makes sense. in my case we currently don't have a need to make copies of entities like this.
  • oh yah - good point. I think when I've said add, i've meant attach :) 
    • the process is currently:
      cloned = MasterContext.Tickets.Single(..).CloneExact();
      IsolatedContext.Tickets.Attach(cloned);
      // user makes some changes
      IsolatedContext.SubmitChanges();
      // I now need to update the original entity in the MasterContext 
      // -- this is because the MasterContext is where the users will be performing searches, viewing lists of tickets, etc. 
      //     if they open a ticket to edit, make changes, and those changes aren't reflected in the list where they edited the ticket from, it's a bug.
      //    - similarly, I could avoid all of this trouble by simply detaching it from the MasterContext and attaching it to the isolated one. 
      //      But this is problematic also, because then the item disappears from their list, and Attaching it back to the MasterContext will not result in it showing up in the list again.
      //      - to further complicate this, the list is sorted, paged, and filtered against the database (using DomainCollectionView)
      //    - I could perhaps not detach or copy the entity into the isolated context, but instead load it (to isolated) from the database, but I still have the problem of then updating the Master Context. 
  • Does this mean that you don't think the nested ApplyState will work?

Detaching the graph from the original context, attaching it to the second, then calling submitchanges, and then attaching the graph to the original context, was also what first came into to my mind. But I understand now that this is not an option.

We need to consult Colin if he knows whether RIA gets confused of there are going to coexists multiple entities having the same keys. I think RIA was designed to only have a single copy of each entity (but I'm not sure). If this is not an issue, than we can easily create an alternative clone method that also copies the keys.

No, I don't mean that the nested ApplyState will not work. In fact, I think it can work very well.

Coordinator
Jan 28, 2011 at 8:05 PM

The question of re-keying entities on a clone is an interesting one. I am thinking about creating a new IKeyManager class that can be passed into Clone methods. The main copy code would no longer clone/copy the keys themselves. Instead, the IKeyManager would do that. There would be a few default providers, one that keeps the existing key for a true clone, one that generates a new one (for GUID keys), and one that leaves the key blank. For entities that uses non-generated keys the user can provide a custom provider to do the work.

What do you two think? The other option was to add the keys to the entities thorugh the IExendedEnity level but I don't think that is flexible enough.

Coordinator
Jan 28, 2011 at 8:07 PM
MdeJ wrote:
of there are going to coexists multiple entities having the same keys. I think RIA was designed to only have a single copy of each entity (but I'm not sure). If this is not an issue, than we can easily create an alternative clone method that also copies the keys.

You can't have two Unmodified entities with the same key. If the key is an identity and the entity is new then they will have an id of 0.

Developer
Jan 28, 2011 at 8:32 PM
ColinBlair wrote:
MdeJ wrote:
of there are going to coexists multiple entities having the same keys. I think RIA was designed to only have a single copy of each entity (but I'm not sure). If this is not an issue, than we can easily create an alternative clone method that also copies the keys.

You can't have two Unmodified entities with the same key. If the key is an identity and the entity is new then they will have an id of 0.

Right -- I would never be attaching an exact clone into the same context.

Colin, this brings up a question I've been wondering about (and is making me consider using Guids for my dependent items, like Notes. But stored in a string field, bleck.)
-- my Ticket id's are generated in the database (oracle sequence). Notes are currently the same way (out of convention, not necessity)
-- What happens if I have more than one unsaved Ticket entity in a context (not that i plan to, hopefully, but ..), with x Notes each?
    -- For x Notes in a context, it's not a big deal: nothing references their IDs: no entities are dependent on a NoteID - but if that were to change .. :-x
    -- How would RIA manage the associations if there is no uniqueness between different Tickets? (Note.TicketID = 0 for all notes)
      -- I notice in my explicit example of ApplyState to the Ticket then to the first Note, that Note.Ticket is null until I add the clone into the isolated context, so I can't imagine it maintains these relationships through parent-instance-comparison.

Developer
Jan 28, 2011 at 8:34 PM

Colin, the IKeyManager  sounds like an interesting idea. But can RIA handle the situation that an entity is in domain context 1, and its clone (with identical keys) is in context 2.  I mean is there any place where RIA can get into trouble because of the existence of two instances of the same entity?

Your latest post suggests that it is indeed a problem. But how does that relate to your IKeyManger idea?

Developer
Jan 28, 2011 at 8:37 PM
Edited Jan 28, 2011 at 8:40 PM
MdeJ wrote:

Colin, the IKeyManager  sounds like an interesting idea. But can RIA handle the situation that an entity is in domain context 1, and its clone (with identical keys) is in context 2.  I mean is there any place where RIA can get into trouble because of the existence of two instances of the same entity?

Your latest post suggests that it is indeed a problem. But how does that relate to your IKeyManger idea?

  • agreed. I'm a little fuzzy on how that it would be implemented, exactly, but I think it sounds like what I'd expect.
  • I can only imagine this might come into play if the 2 contexts are referenced (using AddReference()), but I think (as per Colin's explanation of that feature to me), that for entities of a type that the DomainContext has reference to, it will always refer to its own EntitySet for that type, so it would never 'find out' about the other item, and even if it did, the 2 contexts maintain separate EntityContainers, which is where the uniqueness of a key/id come into play? 
    In my case, with x isolated contexts, they know nothing of one another.
Coordinator
Jan 28, 2011 at 8:56 PM

Joe has it right, two DomainContext of the same type can have nothing to do with each other so two entities with the same key in two separate contexts is fine.

Coordinator
Jan 28, 2011 at 10:14 PM

FYI, I checked in the code after getting the XML comments added and clearing out all of the FxCop warnings. The dll now is now marked as CLS compliant and every single method is now throwing ArgumentNullExceptions is nulls are passed in. Yay.

Developer
Jan 29, 2011 at 8:33 AM

Great! This includes the changes you talked about for quite a while, right? I will soon change EntityGraph's clone method to call your Entity clone method.

Developer
Feb 19, 2011 at 9:27 PM

I've changed the behavior of the Clone method. It will now also clone key and foreign key properties. In addition , I've created a Copy and IsCopy method that has the original Clone behavior.

So, clone now really creates an equivalent entitygraph, while Copy creates an entity graph that has the same structure and values, but newly created keys.

Joe, I think this is in line with what you expected from Clone, right?

Developer
Mar 4, 2011 at 9:40 AM

I've just implemented the EntityGraphChangeSet.ToString() and EntityGraphChangeSet.GetEnumerator() methods to get rid of the NotImplementedExceptions on EntityGraphChangeSet

Jun 2, 2011 at 6:40 AM

This Copy method was very helpful thanks Mdej!