Inspiration
A lot of times when I tell non-developers that building good software is actually a creative activity, more then it is a technical one, they look at me like I'm telling them I'm the Queen of England. However I'm not the only developer who gets stuck every now and then, not so much because I face such a hard to solve problem, but simply because I have a lack of inspiration.
The general consensus is that it helps to put your mind on other things, however in this case I felt I needed to regrasp the spirit of doing cool code. I surfed around a bit and ended up on Channel 9. I browsed trough the latest videos and found a video about Windows 7 and how the kernel no longer uses dispather locks. You can watch it here:
Now, this is obviously not on topic for anything I do, as I'm working on a Line Of Business application. Also a lot of what was being told was completely new for me, because I never had such an in dept look at the Windows kernel, however the way Arun Kishan explores the issues with the dispatcher lock and with possible solutions to the problem, were very inspiring to me.
The issue
I started to describe my issue to myself. We have been building a framework that uses a generic communication model using WCF to pass data between a webservice and our Silverlight client. In this framework we have a single operation called UpdateItem which is responsible for saving any changes we make to the data (except for delete). So if I create a new business object, I pass it to UpdateItem and it persists it to the database. If I update a business object, again UpdateItem is called and it persists those changes to the database.
So far so good. However, I was building a popup in which a user is allowed to add items to a combobox, so it can then use these. I figured I would just create the business object without an ID, which indicates to the UpdateItem operation that this is a new object and it would then persist it in the database. It turns out it didn't. Here is why.
Two types of adding data
In Entity Framework you can add objects to generic ObjectQuery instances which are contained in the ObjectContext. Some digging showed we didn't use this, because in our application we have two different kinds of adding data.
The first type we implemented was adding data trouhg so called 'parent' data. For example you can have a Person object, which can have children. Now if I add a Child, in the framework we've build, it knows about that and sets the Child.Parent property to the right Person object and the Entity Framework detects this as an insert. We never call an Add on the ObjectContext and that's fine, because we added it trough some other object.
However in the situation I described earlier, the newly added object doesn't have any 'parent' data. It is just a sole record in some list somewhere, that may or may not get referenced by other objects. To be able to insert this I would need a second type of insert that would directly call an Add near or on the ObjectContext somehow. More importantly I would need some way to detect if he object actually did or did not have 'parent' data. To figure this out I dove deeper into the framework.
The internals of UpdateItem
If we have a look at our UpdateItem operation, this is basically what it does:
- Deserialize the incomming data
- Instantiate a new instance of the targeted business object
- Load the original data into the business object (if available)
- Call Save with the incomming data to persist it
- Serialize the business object and send the latest state back
All the magic happens in the Save, so let's have a closer look. The original Save consisted only of three parts. It would start with pre-save preparations, which are specific to an object. Then it would make sure that all the changes needed would be in an EntityObject and then it would call Save on the ObjectContext. This works fine for the first type of save, where we had the 'parent' data, but not if there is no 'parent' data.
Chosing a solution
I figured I had several options to insert code that would fix the problem. The first option was to actually have this done in the pre-save preparations. This would allow me to fix the problem quickly now, however there are going to be a lot of these types of objects, that get saved without 'parent' data, so I decided to try and find a more generic solution that didn't need type specific implementation.
The second option was to expand the method that makes sure all the changes to the model are in the EntityObject (called BuildModelData). This would solve the problem, however determining if the object doesn't have 'parent' data is a very complex thing and BuildModelData was already complex as it was. So if I had no other choice I might go for it, but for the moment the search went on.
The final option was to expand Save, so it would somehow check if the object had 'parent' data or not and if not add it somehow to the ObjectContext. It would have to do so after BuildModelData and before calling Save on the ObjectContext. It turned out this was a lot easier then I expected.
The ObjectContext has a property called ObjectStateManager. This object actually keeps track of any changes made to the ObjectContext in any way. It has a method called TryGetObjectStateEntry, which allows you to see if an EntityObject is actually part of the changes made so far. It also provides information about the changes involved, but all I needed to know was if it was part of the changes or not.
Adding the object was even easier. The ObjectContext has an AddObject method, which takes a name and an EntityObject, which I both had already available. Here is the code I ended up adding:
ObjectStateEntry objectState;
bool hasObjectState = context.ObjectStateManager.TryGetObjectStateEntry(entity, out objectState);
if (!hasObjectState)
{
context.AddObject(name, entity);
}
bool hasObjectState = context.ObjectStateManager.TryGetObjectStateEntry(entity, out objectState);
if (!hasObjectState)
{
context.AddObject(name, entity);
}
As you can see, there is not much to it, but the best solutions are simple. However, testing with this I found out that the DeleteItem operation, used for deleting objects, also calls save, but then the EntityObject in entity is actually null (as it was deleted), so the Save would fail (although the object was still deleted from the database as well). A simple check on entity == null fixed that problem and now I can handle both types adding data trough our framework.
I hope you've found this post as inspiring as it was for me to experience and write it. Please leave me comments if there is anything you need or want to share.
No comments:
Post a Comment