EntityBag Part I – Goals

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))

            .First();

        room.Exits.Load();

        return db.CreateEntityBag<Room>(room);

    }

}

public void UpdateRoomAndExits(EntityBag<Room> bag)

{

    using (DPMudDB db = DPMudDB.Create())

    {

        bag.UnwrapInto(db);

        db.SaveChanges();

    }

}

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 .= "***";

}

bag.Delete(bag.Root.Exits.First());

var newRoom = new Room();

newRoom.Name = "NotTheRoomYouAreLookingFor";

var newExit = new Exit();

newExit.Name = "NotTheExitYouAreLookingFor";

newExit.TargetRoom = newRoom;

bag.Root.Exits.Add(newExit);

client.UpdateRoomAndExits(bag);

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.

- Danny

Comments

  • Anonymous
    January 19, 2008
    Did you see the post at blogs.msdn.com

  • Anonymous
    January 20, 2008
    Very cool Danny - I look forward to your following articles. TBH I was surprised to find that the EF didn't have this built in - perhaps it will inthe future? Cheers, Jason

  • Anonymous
    January 20, 2008
    The EF won't have something like this built-in for v1, but we are looking hard at the topic for future releases.  I'm not 100% certain we'll do this, since there are some serious issues when it comes to interoperability, etc. (as I've noted).  I believe we made some major mistakes with the DataSet in this regard, and I don't want to repeat them.

  • Danny
  • Anonymous
    January 20, 2008
    Sounds very interesting. How would this work at the client when you have properties that are lazy loaded and which haven't been loaded when the graph is serialized?

  • Anonymous
    January 20, 2008
    The comment has been removed

  • Anonymous
    January 21, 2008
    Ahhh...right. I haven't had a chance to dig into the EF in a lot of detail yet but am approaching this from an NH background. Do you think the EF will support implicit lazy loading down the track? Jason

  • Anonymous
    January 21, 2008
    The comment has been removed

  • Anonymous
    January 23, 2008
    The comment has been removed

  • Anonymous
    January 23, 2008
    The comment has been removed

  • Anonymous
    January 24, 2008
    Thanks for the response Danny. I'm really not sure I get #1. Accessing a property shouldn't trigger a hit to the db every time you access it. Surely, the first access hits the db and then the result is cached for the duration of the UOW, i.e. datacontext in EF. It would be great if you could also please elaborate on your comment "implicit loading means that it can be very hard to predict whether or not a property access will require a DB roundtrip". I am finding it difficult to see any use case where a developer would want [or should be able to] access a property that was not loaded from persistent storage before first access. Allowing this surely just creates an inconsistency between the db and in-memory right from the get-go which to me seems like a recipe for disaster. Thanks for providing some info on manually implementing a implicit lazy loading in EF. It is good to see that it is possible to get this working at a base level without the developer having to think about every property access although I am strongly in favour of this being an integral part of EF without the need for IPOCO or codegen solutions. Jason

  • Anonymous
    January 24, 2008
    The comment has been removed

  • Anonymous
    January 28, 2008
    The comment has been removed

  • Anonymous
    January 28, 2008
    The comment has been removed

  • Anonymous
    January 28, 2008
    Danny Simmons wrote a class that persists the EntityState of objects in an ObjectContext so that they

  • Anonymous
    January 28, 2008
    Danny Simmons wrote a class that persists the EntityState of objects in an ObjectContext so that they

  • Anonymous
    February 01, 2008
    The comment has been removed

  • Anonymous
    February 03, 2008
    The comment has been removed