We are adding an AttachEntity(object entity) method to the EntityManager in release 5.2.2.
Those of you who write test and don't want those tests to touch the database will appreciate this the most.
As you know, you sometimes need to write tests which rely upon interaction with the EntityManager. You want to populate a disconnected EntityManager with a small collection of hand-rolled stub entities. While such tests are integration tests because they rely on a dependency, we still want to make them easy to write and we want them to be fast. That means we don't want a trip to a database when we run them; we shouldn't need to have a database to run them.
I usually start by creating a test-oriented, disconnected EntityManager ... which can be as simple as
var testManager = new EntityManager(false /* disconnected */ );
The easiest way to get a stub entity is to "new" it up, set some of its properties, give it an EntityKey, and dump it in our testManager. when we're done it should appear there as an unchanged entity ... as if you had read it from the datastore.
The catch is "how do I add the entity to the manager?"
In the absence of AttacheEntity you are likely to have used EntityManager.AddEntity. But after AddToManager, the EntityState of the entity is always "Added". You want "Unchanged" so you have to remember to call AcceptChanges (which changes the state to "Unchanged").
That's not too hard. Unfortunately, it gets messy if the key of the entity is auto-generated (e.g., mapped to a table whose id field is auto-increment) because DevForce automatically replaces your key with a temporary one as part of its auto-id-generation behavior.
I could explain how to work around this. What a PITA. We really just want a simple way to simulate the result of retrieving an entity. That's why we now have AttachEntity.
Here's the XML documentation for AttachEntity:
Adds a detached entity to this EntityManager in an Unmodified state.
Throws an exception if an entity with the same key already exists in the manager
of if the specified entity is not in a detached state.
Let me elaborate here and compare it to some similar methods by calling out the following facts about the following code fragment:
theEntityManager.AttachEntity(object theEntity)
1. theEntity’s EntityKey (“the key”) must be preset prior to the attach operation which will not touch the key
2. An exception is thrown if an entity with that key is already in the cache
3. After attach, theEntity is in an “Unchanged” EntityState (“the state”)
4. theEntity is presumed to exist in the persistent store; subsequent change and save will translate to an update statement.
5. After successful attach, a reference to theEntity is a reference to the entity with that key in the manager’s EntityCache; contrast with anEntityManager.Imports(new [] {anEntity})” as discussed below.
6. theEntity must be in the “Detached” state prior to the operation
7. An exception is thrown if theEntity is other than in “Detached” state prior to the operation
8. After attach, related entities are implicitly associated with theEntity automatically; for example, if anOrder with Id==22 is attached and there are OrderDetails with parent OrderId==22, then after the attach, anOrder.OrderDetails returns these details and any one of them will return ‘anOrder’ in response to anOrderDetail.Order.
9. The sequence of attachments is not important; OrderDetails may be added prior to the parent Order;
10. Attach has no effect on theEntityManager’s QueryCache
AttachEntity and AddEntity behave the same way except:
11. After add, theEntity is in an “Added” state
12. theEntity is presumed to be new and to be absent from in the persistent store; a save will translate to an insert statement.
13. If the key for this type is auto-generated (e.g., backed by an auto-increment field in the database), the existing key will be set to a generated temporary key, replacing the prior key value.
The following is true regarding detaching anEntity:
14. After detach, anEntity enters the “Detached” state no matter what its prior state.
15. Detaching an Order does not detach its child OrderDetails; they remain “orphaned” in the cache
16. The sequence of detachments is not important; an Order may be detached prior to detaching its child OrderDetails
17. Detach has no effect on theEntityManager’s QueryCache
EntityManager.Imports is another way of populating an EntityManager with a collection of entities that may have come from anywhere (including hand-rolled). Here's how you might "import" a single stub entity:
theEntityManager.Imports(new [] {theEntity})
Imports differs from AttachEntity in that:
18. it requires a MergeStrategy to tell it what to do if an entity with the same key as "theEntity" already exists in the cache.
19. it merges "theEntity" into the cache based on the MergeStrategy
20. it makes a clone of "theEntity" and adds that clone to the EntityCache ... unless "theEntity" happens to already be in the cache in which case it is ignored ... which means that
21. using our example and assuming that "theEntity" was not already in the manager, the entity instance in the cache is not the same as the entity instance you imported, although their keys are equal; the following is true:
theEntity != theManager.FindEntity(theEntity.EntityAspect.EntityKey)
22. A "clone" is a copy of an entity, equivalent to calling ((ICloneable)theEntity).Clone();
23. This is a copy of the entity, not of its related entities.
Enjoy!