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.