I was recently asked how I get the context of “this” in the UoW relating to the current page request.
Before I get into the guts, I would like to provide a little context. My application has 20+ databases scattered across 4 machines.
IRepository<Customer> customerRepository; // customer database on server 1 IRepository<Package> packageRepository; // customer database on server 1 IRepository<Contact> contactRepository; // contact database on server 2
public class GlobalApplication : HttpApplication { private static IUnityContainer container; public GlobalApplication() { BeginRequest += new EventHandler(GlobalApplication_BeginRequest); EndRequest += new EventHandler(GlobalApplication_EndRequest); } protected void Application_Start(object sender, EventArgs e) { RegisterRoutes(RouteTable.Routes); container = new UnityContainer(); container.AddNewExtension<PolicyInjectorContainerExtension>(); container.AddNewExtension<HttpRequestLifetimeCoreContainerExtension>(); container.AddNewExtension<WebMvcContainerExtension>(); ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory(container)); } void GlobalApplication_BeginRequest(object sender, EventArgs e) { var unitOfWork = container.Resolve<IUnitOfWork>(); unitOfWork.Start(); } void GlobalApplication_EndRequest(object sender, EventArgs e) { var unitOfWork = container.Resolve<IUnitOfWork>(); unitOfWork.Commit(); } }
I register the UoW with an HttpRequestLifetimeManager so I get a new instance for each request.
Container.RegisterType<IUnitOfWork<ISession>,
NHibernateUnitOfWork>(new HttpRequestLifetimeManager());
public class NHibernateRepository<T> : IRepository<T> { protected ISession session; public NHibernateRepository(IUnitOfWork<ISession> unitOfWork) { session = unitOfWork.GetContextFor<T>(); } ... public virtual void Save(T obj) { session.Save(obj); } }
Now, this is all the context of an ASP.NET MVC Controller, but I have a similar issue for other (non-web) services. In that context I am using AOP and decorating a particular method with [UnitOfWork], which looks like:
public class UnitOfWorkCallHander : ICallHandler { private IUnitOfWork<ISession> unitOfWork; public UnitOfWorkCallHander(IUnitOfWork<ISession> unitOfWork) { this.unitOfWork = unitOfWork; } public int Order { get; set; } public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { unitOfWork.Start(); try { return getNext()(input, getNext); } finally { unitOfWork.Commit(); } } }
In that context I use a PerThreadLifetimeManager for the NHibernateUnitOfWork, and code ends up looking like:
[UnitOfWork] public void Process(Job job) { ... }
You know, there is really no reason why you couldn’t do the same thing in the MVC context. You could basically ditch the global.asax event hooha and just annotate the Controller task:
[UnitOfWork]
public ActionResult ControllerTaskThatRequiresUoW()
{
...
}
It’s more explicit than using the global.asax technique and it would allow you to specify different UoW behavior on each controller task:
[UnitOfWork(IsolationLevel.ReadCommitted] public ActionResult SomeTaskThatShouldNotReadUncommitedData() { } [UnitOfWork(IsolationLevel.ReadUncommitted)] public ActionResult AnotherTaskWithDifferentRequirements() { }
I’d like to try this out next time I get back into ASP.NET MVC, the global.asax event technique felt a little too magical and I don’t think Uncle Bob would approve.
2 comments:
I was wondering if you ended up creating an annotation to do the commit instead of the global.asax method you described.
Yes, I took the unit of work start and commit out of the global.asax. While opening a session isn't a big deal, starting a transaction is and that was happening on every web request.
Post a Comment