If you recall, the original solution was to use an HttpModule to subscribe to the BeginRequest and EndRequest events. When we upgraded to Ninject 2.1, none of our entities would get saved. Reads all worked fine, but no CUD actions worked. The problem turned out to be due to a new component of Ninject called the OnePerRequestModule. This module subscribes to the EndRequest event and disposes any objects that were bound in request scope. The problem was that this module would dispose of our session, before we had a chance to commit the transaction and end the session ourselves. It was also registering first (which meant it was first to handle EndRequest) and there appeared to be no hook to turn it off.
The solution that we came up with was to use a filter attribute in order to encapsulate our unit of work. The first part of the refactor was to create an IUnitOfWork interface:
public interface IUnitOfWork { void Begin(); void End(); }
That's all a unit of work really should look like. Second we moved our BeginSession and EndSession code into an implementation of IUnitOfWork:
public class NHibernateUnitOfWork : IUnitOfWork, IDisposable { private readonly ISession _session; public NHibernateUnitOfWork(ISession session) { _session = session; } public void Begin() { _session.BeginTransaction(); } public void End() { if (_session.IsOpen) { CommitTransaction(); _session.Close(); } _session.Dispose(); } private void CommitTransaction() { if (_session.Transaction != null && _session.Transaction.IsActive) { try { _session.Transaction.Commit(); } catch (Exception) { _session.Transaction.Rollback(); throw; } } } public void Dispose() { End(); } }
This separated repository logic from the unit of work implementation. The binding for this implementation was bound in request scope. Finally we created our filter attribute:
public class UnitOfWorkAttribute : FilterAttribute, IActionFilter { [Inject] public IUnitOfWork UnitOfWork { get; set; } public UnitOfWorkAttribute() { Order = 0; } public void OnActionExecuting(ActionExecutingContext filterContext) { UnitOfWork.Begin(); } public void OnActionExecuted(ActionExecutedContext filterContext) { UnitOfWork.End(); } }
The order was set to 0 to ensure that this filter was always run first. We also did not have to explicitly call End on the unit of work since it would be disposed at the end of the request and would call End itself. However, it's good practice to be explicit with these things. Finally you can apply this filter at the controller level or the action level. So you can decorate only those actions that actually perform data access.