Well, I guess it’s high time that I get down to business of sharing and explaining the “general purpose container object” for transporting graphs of entities along with change tracking information over WCF web services which I mentioned in this previous post. As it turns out, there’s a fair amount of code involved, so we’ll build up a series of routines / classes until we can get to the top-level object which I call EntityBag<T>.
To start us off, though, let’s take a look at the top-level interface for EntityBag and how it would be used. The goal is to write a couple of mid-tier web methods: one which will retrieve a graph of entities in a single call and instantiate them on the client in full graph form, plus one which will take a modified graph back and persist the changes to the database while honoring the optimistic concurrency contract based on the original values retrieved and without requiring extra round trips to the DB in order to save. To make the whole thing work, we need:
1) A way to send the entire graph in spite of the fact that the EF and WCF by default only shallowly serialize entities (that is related entities are not automatically serialized).
2) Something which will track changes on the client—both changes to regular properties of the entities as well as changes to the graph (new entities, deleted entities, modified relationships).
3) A serialization format that can include not only the current values of an entity but original values and information about the state of the entities, etc.
The strategy I adopted for EntityBag is to create a DataContract serializable object which will effectively transmit an entire ObjectStateManager and to identity a single object as the “root” of the graph. This way on the mid-tier a graph of related entities can be retrieved from the database into an ObjectContext, an EntityBag may be constructed to hold that context, and then that bag can be serialized to the client. On the client, the EntityBag will expose a single public “Root” property and internally will create a private ObjectContext which is used both to help reconstruct the graph and to transparently track changes. When it’s time to persist the changes, the EntityBag can be serialized back to the mid-tier and the updated entity graph along with all original values can be reconstructed into a mid-tier context which will then be used to save the changes to the DB.
Code for the web methods might look something like this:
public EntityBag<Room> GetRoomAndExits(string roomName)
using (DPMudDB db = DPMudDB.Create())
roomName = '%' + roomName + '%';
var room = db.Rooms.Where("it.Name like @name", new ObjectParameter("name", roomName))
public void UpdateRoomAndExits(EntityBag<Room> bag)
using (DPMudDB db = DPMudDB.Create())
As you can see, the code in these two methods is uncluttered by serialization considerations—just one call (CreateEntityBag) in the first method and one (UnwrapInto) in the second method encapsulate the plumbing. The client side is similarly straight-forward. Essentially all that is necessary is to use the Root property on the EntityBag to access the graph:
EntityBag<Room> bag = client.GetRoomAndExits("Calm");
foreach (Exit e in bag.Root.Exits)
Console.WriteLine("\t" + e.Name);
e.Name .= "***";
var newRoom = new Room();
newRoom.Name = "NotTheRoomYouAreLookingFor";
var newExit = new Exit();
newExit.Name = "NotTheExitYouAreLookingFor";
newExit.TargetRoom = newRoom;
While I like the simplicity of this interaction, it is super important to keep in mind the restrictions imposed by this approach. First off, there’s the fact that this requires us to run .Net and the EF on the client—in fact it requires that the code for your object model be available on the client, so it is certainly not interoperable with Java or something like that. Secondly, because we are sending back and forth the entire ObjectContext, the interface of the web methods imposes no real contract on the kind of data that will travel over the wire. The retrieval method in our example is called GetRoomAndExits, but there’s absolutely no guarantee that the method might not return additional data or even that it will return a room and exits at all. This is even scarier for the update method where the client sends back an EntityBag which can contain any arbitrary set of changes and they are just blindly persisted to the database. The ease of use we have achieved comes at a very high price. Of course if you are writing web services that transport instances of the DataSet, you already live in that world, and for some scenarios this might be acceptable.
OK. Now that I have your attention, in future posts we can dig into the implementation of EntityBag.