Unit of Work in Rich Clients
When it comes to dealing with databases, smart .NET developers follow the unit of work pattern. In NHibernate, the unit of work is an ISession. In LINQ to SQL and Entity Framework, it's the DataContext/DbContext.
When implementing a unit of work, or in fact any object, lifetime matters. When is the unit of work created? When does it end?
When writing ASP.NET or WCF applications, the lifetime of a unit of work is usually the request/response cycle. A single ASP.NET MVC controller might touch multiple repositories, but ideally it should only involve one ISession. The request/response cycle also often serves as the database transaction boundary.
Though some ASP.NET/WCF applications may hold a unit of work open for longer than a single request/response, it's generally agreed that it is a bad idea. Request/response is almost always the perfect model.
Unit of work in rich clients
Rich client applications don't tend to be so simple. Often, the lifetime of your unit of work depends on the user experience you're implementing. Here are some examples from applications I've worked on.
Short - Unit of Work per Interaction
In this search screen, my unit of work can be very small. When the search button is clicked, I open an ISession, fetch some results, and close the session immediately. The unit of work should only be open for a few milliseconds to a second.

This unit of work model is usually the easiest to implement, though it can mean you can't lean on the ISession for change tracking and other useful features.
Medium - Unit of Work per View
In this bulk edit screen, I might open an ISession, fetch some results, and keep the session open while the user edits the changes. When they click Save, I'll commit the ISession. My unit of work might be open for a few seconds to a few minutes.

This unit of work model is tricker to implement, since you need to have well defined "close" points. If your views are Windows/Dialogs, that's easy. If you're using WPF pages, it's harder, since the page can remain in the back/forward stack for some time. You need to think hard about when the right time to close each unit of work is.
Long - Unit of Work per Workflow
In this wizard UI, I might build a unit of work that stays open for the entire business process, growing larger and larger as the user makes their way through the various screens.

This unit of work model is probably the hardest to manage, since you need to find a nice way to share the unit of work between views/view models. If you only have one workflow active, making your unit of work a singleton could work. But if you have multiple workflows active at once, each with their own unit of work, it's harder.
Unit of work in Magellan
Magellan, like ASP.NET MVC, encourages you to use the "short" unit of work approach by default. For example:
public class SearchController : Controller
{
public ISession Session { get; set; }
public ActionResult Search(string text)
{
var results = Session.QueryOver<Customer>()
.Where(Restrictions.Like("Name", text))
.List();
return Page(new SearchViewModel(results));
}
}
When a Controller is resolved, its dependencies, including an ISession, might be injected. The controller might use the session to fetch some information, populate a view model, and render a view. By the time the view appears on screen, the Controller and its dependencies, including the ISession, will have been disposed.
Generally, I've found WPF applications are much easier to implement when the unit of work is scoped to a single request.
You can make Magellan support the other two unit of work models I've described above, by making a few hacks in a custom IControllerFactory, but I'd encourage you to consider the short unit of work model.
The short unit of work model also works well if you intend to someday switch your application to go through a service layer.
Why MVC in WPF makes sense
When you use MVVM by itself, the boundary around a unit of work is never clear, and might be inconsistent from view to view.
In Magellan, you use a combination of a MVVM and MVC. The controller formalizes the scoping a request - it's perfect for navigation and invoking external services. We still use a ViewModel, but it only deals with state and behavior - integration and unit of work is managed by the controller.
The key point of this post is that unit of work in WPF applications can be more complicated than web applications. I personally find the separation that MVC encourages is a great way to make me think hard about how units of work are managed, while implementing it in a consistent way.
Trackbacks
- Android Phone Video:NVIDIA quad-core Tegra 3 “Kal-El” quad-core processor demo blows us away | Android Phone Review | http://www.androidphonesreview.info/android-phone-videonvidia-quad-core-tegra-3-kal-el-quad-core-processor-demo-blows-us-away/
- Dew Drop – February 16, 2011 | Alvin Ashcraft's Morning Dew | http://www.alvinashcraft.com/2011/02/16/dew-drop-february-16-2011/
- http://topsy.com/www.paulstovell.com/unit-of-work?utm_source=pingback&utm_campaign=l2 | http://topsy.com/www.paulstovell.com/unit-of-work?utm_source=pingback&utm_campaign=l2
- Linksammlung 24/03/2011 | Silverlight, WPF & .NET | http://www.ebnerj.at/blog/?p=1143
- The Morning Brew - Chris Alcock » The Morning Brew #794 | http://blog.cwa.me.uk/2011/02/17/the-morning-brew-794/
- Unit of Work in Rich Clients | www.nalli.net | http://www.nalli.net/2011/02/unit-of-work-in-rich-clients/
No new comments are allowed on this post.
Comments
Nicholas Blumhardt
Magellan/MVC has changed the way I think about implementing rich client apps - after seeing it in action it now seems obvious to me that Controllers are the puzzle piece that most rich clients are missing.
Taking the 'transactional' logic out of View Models also makes them much more focused in their responsibilities.
I'd love to hear more about your experiences with this pattern in different situations - I'm really enjoying learning some more disciplined approaches to lifetime in rich client apps.
Caleb Vear
Generally I use IStatelessSession instead of ISession if all I am doing is loading objects for a search/display and don't need any state change tracking.
Darren Neimke
I like the way you describe that the Controller.Action formalizes the boundary of the unit of work to be done. So Controller.Action == UnitOfWork.DoWork.
I think that there needs to be some consideration as to how the state of ISession is scoped. For example, what implications might arise if I pass ISession to a child component. And even further, what if that child component is then cached?
Darren Neimke
I should clarify my previous comment... you say pretty clearly that the unit of work is not Controller.Action (that only forms a logical contextual boundary) but the unit of work is more like:
My comment about the scoping of ISession is still relevant though
Paul Stovell
Hi Darren,
Generally the ISession shouldn't be passed to other components, but should be injected into them via the container. So for example, suppose my
Repository<T>looks like this:My controller could then look like this:
In my IOC container, I'd register the components with the following lifetimes (using Autofac terminology):
My
ControllerFactorywould create a lifetime scope per controller it resolves, so that every repository in the controller gets the sameISession.If another component needed access to the session within the controller action, then that component would take a dependency on
ISessionand it would be resolved from the container just like repositories do. They're all resolved within the same lifetime scope so they'll share the same session.Paul
Darren Neimke
Thanks for the response. I think extending the discussion about lifetimes would be good.
Oleh
What prototyping tool you have used? Found some new done with Silverlight, you might take a look www.mockupbuilder.com
Martin
"Generally the ISession shouldn't be passed to other components, but should be injected into them via the container."
I don't like this sort of dogmatic thinking. It leads to overly complex designs and fails to utilize the power of the ISession/Datacontext.
For example, if you have an OrderViewModel and OrderItemViewModel, the most elegant way is to have the repository inject the ISession/Datacontext into the OrderViewModel which in turn injects the ISession/DataContext into the OrderItemViewModel.
The OrderViewModel then takes care of saving / transactions for the entire order and there is no need to co-ordinate anything as it will be implicitly handled by the ISession.
Paul Stovell
Hi Martin,
I agree that not everything fits neatly into that basket. In the cases where it doesn't the rule would be:
Which I suppose is a good rule to follow when it comes to managing any object lifetime.
Now, I could be nit-picking your example but:
OrderItemViewModelfrom the container too?OrderItemViewModelreally need the fullISession? Wouldn't it just wrap a realOrderItem?OrderViewModelpass depenencies to theOrderItemViewModel, aren't you doing the job of the IOC container?I think a lot of it depends on your overall application architecture. In a Magellan application, an
ISessionwould never be passed to aViewModelin the first place.Paul
Martin
I agree with your first rule, that the child (OrderItemViewModel) should not outlive the unit of work.
Could you resolve the OrderItemViewModel from the container too?
It follows that the OrderItemViewModel would not be resolved from the container, as it probably has no purpose as a standalone (eg it doesn't know how to fetch itself, save itself....)
If not, does the OrderItemViewModel really need the full ISession? Wouldn't it just wrap a real OrderItem?
It might not be that common for the OrderItemViewModel to need the full ISession, however there are certainly cases where it makes for a cleaner implementation. For example, lets say an OrderItem has a property called DeliveryMethod which is a lookup table in the database. I prefer the OrderItemViewModel to populate a collection (for the view to bind to) of DeliveryMethods from the ISession rather than require the OrderViewModel to pass them in. There are many other examples where the OrderItemViewModel might need other adhoc stuff from the ISession.
By having the OrderViewModel pass depenencies to the OrderItemViewModel, aren't you doing the job of the IOC container?
I have a different philosophy here. Injecting dependencies via constructors has been sound software engineering long before IOC containers existed, therefore I don't think everything that gets injected into a constructor has to come from the IOC container. Particularly in this case the ISession is created by the IOC, but just propogated from parent to child.
The whole point of my argument is to keep it simple. There is no need to co-ordinate the units of work, as it happens implicitly. However, you still get the benefit of encapsulating as much of the OrderItemViewModel related logic in its own class.
To be honest, I haven't looked at Magellan (yet), I was just discussing the general point. I am interested to see how you separate the VMs from an ISession (without creating a lot of work :-)