Unit of Work in Rich Clients

This is a very old post. Some of the images and links may no longer work. The text is preserved here for posterity.

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.

A search screen

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.

Bulk edit

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.

A navigation centric wizard

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.