Restoring Domain Objects from a Database: An Identity Problem
We are using Linq to SQL to read and write our domain objects to a SQL Server database.
We provide a range of services (via WCF) to perform various operations. In the end, the implementation of these operations consists of three stages: restoring the necessary domain objects from the database; perform an operation on domain objects; save the (now modified) domain objects back to the database.
The problem is that sometimes there are two or more instances of the same entity object, which can lead to inconsistency when saving the objects back to the db. A slightly compiled example:
public void Move(string sourceLocationid, destinationLocationId, itemId);
which should move the element with the given id from source to destination (actual services are more complex, often in many places, elements, etc.). Now it could be that both source and destination location IDs are the same - a naive implementation would just recreate two instances of the entity object, which would lead to problems.
This issue has now been "fixed" by manually checking it, ie. we restore the first location, check if the second identifier is different from it, and if so, repeat the second, etc. It is opaque and error prone.
Anyway, I was really surprised that there doesn't seem to be a "standard" solution for this in a domain driven project. In particular, repositories or factories don't seem to solve this problem (unless they maintain their own cache, which then needs to be updated, etc.).
My idea would be to make the DomainContext object per operation that monitors and caches the domain objects used in that particular method. Instead of restoring and saving individual domain objects, such an object will be restored and saved as a whole (perhaps using repositories), and it can act as a cache for the domain objects used in that particular operation.
Anyway, it looks like this is a common problem, since how is it usually seen? What do you think of this idea?
a source to share
The DataContext in Linq-To-Sql supports the Identity Map concept and needs to cache the objects that you retrieve. The objects will be different unless you use the same DataContext for every GetById () operation.
Linq to Sql objects don't really act outside the lifetime of the DataContext. You can find Rick Strahl's Linq to SQL DataContext Lifetime Management which reads well.
Also, the ORM is not responsible for the logic in the domain. This will not preclude your example Move
. It's up to the domain to decide what that means. Is he ignoring it? or is it a bug? This is your domain logic and needs to be implemented at the service boundary that you are creating.
However, Linq-To-Sql knows when an object changes, and from what I've watched, it won't log changes if you re-assign the same value. for example if Item.LocationID = 12, setting location ID to 12 again will not trigger an update on call SubmitChanges()
.
Based on the above example, I would be tempted to go back early without loading the object if the source and destination are the same.
public void Move(string sourceLocationId, destinationLocationId, itemId)
{
if( sourceLocationId == destinationLocationId )
return;
using( DataContext ctx = new DataContext() )
{
Item item = ctx.Items.First( o => o.ItemID == itemId );
Location destination =
ctx.Locations.First( o => o.LocationID == destinationLocationID );
item.Location = destination;
ctx.SubmitChanges();
}
}
Another small point that may or may not apply is that you should keep your interfaces as short as possible. For example, if you normally do 10 move operations at the same time, it is better to call 1 maintenance method to do all 10 operations at the same time, instead of 1 operation at a time. ref: chunky vs chatty
a source to share
Many ORMs use two concepts which, if I understand you, addresses your problem. The first and most significant is the context, which is responsible for the fact that only one object represents an entity (a row of a database table, in the simple case), how many times or paths it has requested from the database. The second is the Unit of Work; this ensures that the database updates for the group of objects are either all successful or fail.
Both are implemented with the ORM I am most familiar with (LLBLGen Pro), however I believe NHibernate and others implement these concepts as well.
a source to share