As I've posted earlier, Billy McCafferty has written an excellent article on using NHibernate within ASP.NET applications.
In this article, he describes how to use the 'Open Session in View' pattern. This is perfectly useable and a very clean way of working with NHibernate in a DDD ASP.NET application, unfortunately, it is -imho- less (or not) useable in a WinForms application.
The 'Open Session in View' pattern implies that a new NHibernate ISession object is created at each request, and closed at the end of each request. (Changes are committed as well).
It is very important that the Repositories keep their hands off of Transaction Management. In other words: a repository should not start, commit or rollback a transaction. This is natural, since the repository doesn't know anything of the 'context' of the Unit of Work. Therefore, transaction managment should be a responsability of the client (the UI, the Service layer, whatever).
To achieve this, Billy stores the current ISession in the CallContext or HttpContext (he has made abstraction of which implementation is used offcourse. CallContext is used in the case of a WinForms app or a Unit-Test project, HttpContext is used in case of an ASP.NET app; see the source-code that accompagnies the article for more details).
In this way, the repositories can easily access the current ISession when they need it by just getting it out of the CallContext or HttpContext, and the ASP.NET application (the ASP.NET webpages) have access to the ISession as well, so they can start, commit and rollback the unit-of-work.
This is the ideal solution in ASP.NET, since you only need one 'ISession' on each request. At the end of the request, the Session can be closed (committed / rollbacked), so they're short-lived, and this is perfect.
However, in a WinForms application, you'll probably want more then one 'Unit of Work' at the same time, and therefore, have more then one open ISession at the same time.
An example will clarify this a bit more:
Suppose you've a WinForms application which consists of a form which lists all Customers. You can edit a customer by double-clicking it; a 'Customer-Detail' form will open up, where you can make changes to the Customer.
Now, what happens if you edit a Customer ? You'll retrieve the ISession from the CallContext, load the Customer from the DB, make some changes and confirm the changes; closing the form will close the ISession.
So far, no problem. However, what happens if you open another Customer-Detail form before you've committed the changes to the first Customer ?
What if you have 2 Customer Detail Forms open, make changes to Customer 1, save the changes and close the form. Afterwards, you want to save the changes to Customer 1... The problem now is that you've closed the ISession when you've closed the first Customer-Detail form. Customer 2 was attached to the same session which is now closed. If you want to save the changes to customer 2, you'll need an ISession, so you'll require a new ISEssion in this case. Then, NHibernate will think that Customer 2 is a new Customer, and will try to insert a new record to the DB where in fact, the existing Customer needs to be updated.
In my opinion, it is better to have a seperate Unit Of Work (ISession) for each instance of the CustomerDetail form in this case.
This implies that you should create a new ISession for each instance of the CustomerDetail form: this means that the ISession cannot be kept in the CallContext since otherwise, each form would share the same ISession.
Then... how should this be solved ? In my humble opinion, the best way to solve this is to create an ISession when an instance of a form is created, pass it to the repositories, and use it to handle the transaction(s) in the form:
public class CustomerDetailForm : Form
{
private ISession _unitOfWork;
private CustomerRepository _custRep;
private Customer _customer;
public void ShowCustomer( int customerId )
{
_unitOfWork = AppSettings.Instance.SessionFactoryObj.OpenSession();
_custRep = new CustomerRepository(_unitOfWork);
_customer = _custRep.GetCustomer(customerId);
// Disconnect from the DB, since we do not need DB action at this time
// anymore.
_unitOfWork.Disconnect();
}
public void OKButton_Click(object sender, EventArgs e )
{
// Reconnect to the DB
_unitOfWork.Reconnect();
ITransaction tx = _unitOfWork.BeginTransaction();
_custRep.Save (_customer);
tx.Commit();
}
}
I don't know if there's any better / cleaner way of doing this... If you know one, please let me know. :) It is offcourse cleaner if you could abstract the use of NHibernate's ISession by creating a class which encapsulates the ISession, but I wanted to keep things a bit sparse. :)