Tuesday, 16 April 2013

Ambient Session and Transaction Management with TinyNH, the Simple NHibernate Infrastructure

So far, you've:

- Created your domain model entity classes
- Added a ConfigurationBuilder to initialise your NHibernate Configuration and class mappings
- Added a ConfigurationStore as an application-wide point of access for your Configuration and ISessionFactory
- Maybe added some automated database tests

It’s time to start writing some code to load and save our objects!

When working with NHibernate, the central point for accessing your objects is the NHibernate ISession. You can load individual objects using the Get method, query using a range of mechanisms and save new / modified objects to the database. The session really is where it all happens.

It is also a good practice to execute your work in the scope of a transaction. This applies even when you aren't saving any changes to your data. A transaction is an important consistency boundary when reading data, as well as writing it (see this article for further information).

In a very simple application, you can work directly with an ISessionFactory and create your own session and transactions. The TinyNH TinyNH.DemoStore.ProductImporter console application provides an example of how you might work with NHibernate in a simple "throwaway" application. Here, the session and transaction are managed explicitly:

Look at ProductImporter/Importer.cs on github if you can't see the code in-line above.

Context Sessions

The application code shown above takes full responsibility for the session life cycle  from opening the session and beginning a transaction to committing and rolling back the transaction.

In other scenarios, it makes sense for the session and transaction to be managed externally to the code that uses the session to do its work. To do this, we introduce a mechanism that makes an “ambient” session available in the background to anything that needs to use it. The life cycle of this session is tied to a given scope or context. One obvious scope would be the lifetime of a web request within an ASP.Net application. Other examples include an NServiceBus handler or similar command handler, anywhere where objects are loaded and saved within a logical “unit of work”.

Sharing a session within a given context brings a number of benefits:
  • You don’t want to have repetitive session and transaction management code scattered throughout your application code.
  • It is less efficient for separate pieces to start and close their own session when the work could be completed within a single shared session.
  • NHibernate's first-level cache can be leveraged more effectively, reducing the number to trips to the database if separate pieces of code need the same object
  • NHibernate's lazy loading mechanism - where an object’s related objects are loaded on-demand as properties and collections are navigated - requires a session to be kept active in the background, perhaps after the method responsible for loading the objects has exited. For example, an ASP.Net MVC controller action method may load some objects and assemble a  model for display in the view. When the view is rendered, child objects associated with objects loaded by the controller may need to be loaded as they are accessed within the view, a good example of where a session needs to be managed outside of the code that uses it. 
Bear in mind that there may be scenarios where certain objects absolutely should be saved in a separate session and transaction, e.g. you are logging user actions at the beginning of a web request and you don’t want this to be rolled back as a result of an error occurring further down the line.

Context Session Management

We need two mechanisms in place to make our ambient session available during a given context:
  • Somewhere to store the current session
  • Something to co-ordinate everything, starting the session / transaction when the scope begins, committing the transaction when the scope ends or rolling back the transaction if an error occurs.
The first element is built-in to NHibernate, in the form of ISessionFactory's GetCurrentSession method. See the documentation about context sessions for background.

NHibernate must be configured with the strategy that it will use to store the context session. ConfigurationBuilder, responsible for initialising our NHibernate Configuration initialises our configuration to use CallSessionContext, a sensible default for tests and offline applications. This is overridden in our web application start-up code to use an implementation of ICurrentSessionContext for web applications:

Look at ConfigurationBuilder.cs on github if you can't see the code in-line above.

LazyWebSessionContext is TinyNH's custom implementation of ICurrentSessionContext. It supports a mechanism whereby starting a session and transaction is deferred until it is actually used. This ensures that we don't pay the price of opening a database connection and starting a transaction during a request that doesn't need to access the database.

NHibernate can't get the current session by itself however. The next piece in the jigsaw is a mechanism to coordinate the session in line with the lifetime of our context.

This is handled by LazyWebSessionContextModule.cs, an HttpModule:

Look at LazyWebSessionContextModule.cs on github if you can't see the code in-line above.

An HttpModule is not the only mechanism available. You could add session management functionality directly to the Application_BeginRequest, Application_EndRequest and Application_Error methods of your global.asax file.ASP.Net MVC ActionFilters and the Controller class itself also provide hook methods that could be used in a similar way. An HttpModule is a useful one-size-fits-all mechanism however, you may be running MVC controllers, Web API controllers and ASP.Net handlers in your application.

Accessing the Session

With the above in place, NHibernate, LazyWebSessionContext and LazyWebSessionContextModule will work together to manage your sessions and transactions. It remains to add a point where your application code can access the current session. In keeping with it's structurally simple architecture, TinyNH adds a CurrentSession property to Global.asax.cs and an additional convenience property to a shared controller base class AppController.cs. There are of course other mechanisms that could be used to make this dependency available to classes that need it - just configure your IoC container to resolve the ISession from the ISessionFactory (an example of how to do this may be added at some point).

How to do it

If you are using NHibernate in a web application, you can add lazy context session management as follows:

1. Add LazyWebSessionContext.cs and LazyWebSessionContextModule.cs to your project, within the same namespace as the ConfigurationBuilder class and other NHibernate infrastructure code.

2. Modify your web application start up code, using ConfigurationBuilder to configure NHibernate to use LazyWebSessionContext - see Global.asax.cs

3. Configure your application to use the LazyWebSessionContextModule HttpModule by adding the module to the configuration/system.webServer/modules section - see web.config for details.

If you are using IIS6 or need to run your web application in the older Visual Studio Development Server, then the module needs to be added to the configuration/system.web/httpModules section instead.

4. Define a point where your application code can access the context session as outlined above.

No comments:

Post a Comment