To Entity.Save() or not to Entity.Save()?

Yesterday, Jimmy Nilsson blogged about an interesting dilemma. I'll quote a snippet from his blog:

Assume you have a domain model instance for a customer and the customer has a number of orders. In the user interface the user adds a new order for the customer, but doesn't press the save button. Then the user edits the customer instance and saves it, and finally the user decides not to save the new order. Without taking extra care in the application, the new order was probably saved too, even though the user never intended it. The reason is that the order was dirty and was in the object graph that was sent to the Service Layer to be saved when the customer instance was sent.

He comes up with a new method, ToBeSaved(), and that is the explicit save marker: if that method isn't called, the entity isn't saved. This got me interested. I wondered: "Why do you need to add this method, if the problem is somewhere else?". The core problem here is: what is the definition of a relationship between two entities?

A relationship between two entities is a semantic relationship: because two (or more, if you have a relationship over 2 or more fields per entity) fields (attributes) are related to each other as in: they hold the same semantic value, the entities they belong to have a relationship, over the field relationship. In the example above, quoted from Jimmy's blog, the order and the customer have a relationship. However will this relationship exist when the order and the customer entities are saved separately? Yes. The reason for this is that the relationship is, as said, semantic: the order entity will hold a value which is a foreign key to the primary key of the customer entity, linking them together.

The short solution would be: simply save the customer, and save all orders which have to be saved and you're done. Because the dirty, new order object doesn't have to be saved, it isn't saved, problem solved. However the problem which made Jimmy search for a solution is bigger: when you save the order and the customer separately, you don't have the order object in the orders collection in the customer object in memory. Now, is that a problem? It is, when you want to work with entities in memory, instead of in the central repository: the database.

Consider two appDomains with the same application. Both target the central database with customers and orders. Two appDomains in the same application isn't rare, when two instances of the same winforms application are run on two desktop machines, two appDomains are running the same application. User A works on one machine with the application, user B on the other. User A adds an order to the current customer and the application is set up in such a way that the order object is added to the orders collection of the customer object. A's application thus sees the new order object in the orders collection of the customer object. B is also working with the same customer (or at least, has it in memory), and doesn't see the change in the orders collection of that particular customer. Since the applications run in two appDomains, a global object cache per appDomain, which is often used by O/R mappers, is not solving anything.

The best way to do this is to rely solely on the shared repository, in this case the database. A should save the new Order when it is done adding it, and B should first consult the database before assuming what the current state of the orders collection of a given customer is. However, it will always be possible to setup a situation where this will fail too. Best of all: avoid multiple edits on the same data and/or implement signalling of mutations between appDomains or at least instances of the same application which all target the same database / shared repository. Yukon will offer this signalling. Till then, the developer should implement a central object cache which is shared among appDomains himself.

2 Comments

  • YOu are right.





    More crreclty. Jimmy is wrong.





    He should have handled both forms with different transactions, diferent objects, different caches etc. Then he never would get the problem, and both things could be commited separatly.

  • The problem with one cache per appDomain still exists though if you use separate caches. (or to be more precise: when a cache is used, it will fall apart when multiple appdomains are accessing the same shared repository)

Comments have been disabled for this content.