New Posts New Posts RSS Feed: Entity Cloning
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

Entity Cloning

 Post Reply Post Reply
Author
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 Topic: Entity Cloning
    Posted: 21-Aug-2009 at 12:33pm
I received an interesting question about how to clone an entity. Here's the thread
-------------------------------

We've an entity for which changes cannot be written, they instead cause a new record to be created and the old one to be deactivated.

 

Simplest way in my mind was to edit the entity as per usual, then in the Saving event handler check if the entity's changed, convert it into a new entity instead, undo changes on the original, and mark it inactive.

 

Straight forward right?

 

// in a partial class of the Employee entity

if(EntityAspect.EntityState != EntityState.Modified) {

  // Do something here to generate a new entity based on the current state of this one...

  // something like:

  var empClone = this.CloneNew(); // How do I "CloneNew"?

  // Revert changes.

  EntityAspect.RejectChanges();

  // Deactivate

  RecordStatusType_fk_ID = (int)EnumRecordStatusType.Inactive;
}

 

Tried a few routes, all failed miserably. At present it has the code to clone the entity manually, but that just seems super ugly. Was hopeful with the CloneCore method, but that kept the key which I couldn't figure out how to reset to new.
-------------------------

I think you’ll find what you need in the test example below.

    [TestMethod]

    public void CloneEntity1() {

      var emp = _em1.Employees.First();

      Assert.IsTrue(!emp.EntityAspect.IsNullEntity);

      var empClone = (Employee) (emp as ICloneable).Clone();

      Assert.IsTrue(empClone.EntityAspect.EntityState.IsDetached());

      Assert.IsTrue(empClone.EntityAspect.EntityKey == emp.EntityAspect.EntityKey);

 

      try {

        empClone.EntityAspect.AddToManager();

        Assert.Fail("shouldn't get here");// because emp is already in cache with this id

      } catch {

        Assert.IsTrue(empClone.EntityAspect.EntityState.IsDetached());

      }

      emp.EntityAspect.EntityManager.GenerateId(empClone, Employee.IdEntityProperty);

      Assert.IsTrue(empClone.EntityAspect.EntityKey != emp.EntityAspect.EntityKey);

      empClone.EntityAspect.AddToManager();

 

 

    }

 
Note that the cloned entity is detached and carries the source entity’s key. You give it a new key BEFORE attaching to the EM (you can’t re-assign the key of an attached entity … at least as of release 5.2.2).

 

It does NOT clone the entity’s graph. We will correct the XML documentation that could be construed to suggest we do. It wouldn’t be obvious how we should do it if we wanted to; once you start following the edges of the graph it’s impossible to know where the business semantics would tell you to stop.

 

Accordingly, YOU are responsible for attaching any entities that are dependent upon the new clone.

 

However, most of the time, you’ll be doing this with leaf nodes and you won’t have to do anything. For example, if I clone an OrderDetail, it will be automatically belong to the parent Order of the source entity … which is probably right. It’s reference to a Product is probably correct too.

 

The challenges begin when you clone an Order. It will not have any child OrderDetails. Should it? Should it not? There is no right answer; it depends upon what you want to do.

 

If you decide to clone the OrderDetails too, you will have to attach the clones to their new cloned parent Order. You might want to do all this fix-up while all of the clones are detached and then add them to the EM as a group.

 

There are lots of potential complexities here as you might imagine. This should get you going.

 

Send in the clones J

-------------------------

Cool, that's definitely cleaner. Any way to generate the id without passing in the data entity property?

 

I'd tried the same using the CloneCore() method, but it gave me an error during the save process having to do with not being able to fix up all the keys.

-------------------------

CloneCore is a protected member and an example of the Template Design Pattern. It is never called directly by consumers of a class. Instead, it is called within a (typically public) member of the base class which incorporates the method as one of the steps within a fixed algorithm. That’s why you call Clone(), not CloneCore().

 

It is virtual to your entity in case you want to add additional behavior during cloning.

 

Per .NET standard, a Clone() method, when provided, is only accessible through an explicit implementation of the ICloneable interface … which is why you cast to ICloneable to get at it. IdeaBlade.EntityModel.Entity, the base class of your entities, inherits from EntityWrapper which implements Clone() [a pretty simple method as you can see with Reflector] which calls CloneCore().

 +++

Now I want to talk a bit more about the sequence of steps by which you create a clone of an existing entity and then add it to an EntityManager.

 

It turns out that the proper place of AddToManager in the sequence of cloning steps depends upon whether or not the key of your entity is auto-generated.

 

CASE A: EntityKey must be generated explicitly

 
If the key is NOT auto-generated, you are responsible for adjusting it prior to adding the clone to manager.  

 

That’s what you see in the code sample that I sent you.

 

      // Assume empClone.Id == emp.Id == 21

      emp.EntityAspect.EntityManager.GenerateId(empClone, Employee.IdEntityProperty); // (1)

      empClone.EntityAspect.AddToManager();// (2)

 

The Id is reset with a temporary Id in step (1). Here is why. The Employee entity’s key is supposed to be  generated via special logic involving a counter table in the database; that logic is inscribed in a custom method (typically in the Domain Model project) that implements IIdGenerator (you can read about in the documentation). The call to GenerateId invokes this logic on the client-side and alerts DevForce to keep track of references to this temporary id (e.g., child entities that become associated with this employee parent).

 

CASE B: EntityKey is set manually to a value

 

Sometimes the Id of an entity is the same as another entity. For example, if Employee and Person are in a 1-to-1 relationship with Person as the “parent”, then Person.Id is generated and the key of Employee (say, Employee.PersonId) receives the id of its associated Person entity.  The employee clone code might look like this:

 

      // Assume person.Id == 21 but personClone.Id has been updated and is now 42;

      // Assume empClone.PersonId == emp.PersonId == 21 ... and we are not fixing that;

      empClone.PersonId = personClone.Id; // (1)

      empClone.EntityAspect.AddToManager();// (2)

 

In step (1) we are changing the Employee’s key from 21 (it’s cloned value) to the new value, 42.

 

SUPPOSE we failed to change the cloned Employee’s key in the above examples. That is, we either failed to ask DevForce to generate the key or failed to set it explicitly. Then the cloned Employee would have the same key as the source Employee in cache.

 

Therefore, when we call AddToManager we should get an exception because we can’t have two entities in the cache with the same key. Try it … and you will see the exception.

 

HOWEVER !!!

 

CASE C: EntityKey is auto-generated

 

In your shop, most of your tables have auto-increment primary key Id fields which means DevForce will auto-generate keys for entities mapped to these tables. The AddToManager code path is different for these entities; it replaces the clone key with a temporary key … whether you’ve set the key or not. Thus, in this sequence

 

      empClone.ID = 21; // (1)

      emp.EntityAspect.EntityManager.GenerateId(empClone, Employee.IdEntityProperty); // (2)

      empClone.EntityAspect.AddToManager();// (3)

 

The ID will change three times

1.       To 21

2.       To a temp id, e.g., -100

3.       To another temp id, e.g., -101

 

You may conclude for such entities that it is pointless to set the id.

 

It follows also that you won’t get an exception if you simply add the clone to the manager … because DevForce ensures it get a unique key before adding the clone to cache.

 

Conclusion:

 

As with entity creation, you must know how the EntityKey is managed before you can clone one and add the clone to the manager.

Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down