I was revisiting what I did to make StructureMap work with UoW per Http and UoW per quartz job and I decided to share my solution here.
So the idea was that I wanted to use StructureMap Hybrid scope to get an instance of UoW when there is a http context and also get a different instance of UoW per thread when there is no http context (like when a quartz job fires). Like this:
For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<UnitOfWork>();
The UoW for http worked fine. The problem was UoW per thread.
Here is what happens. When a quratz job fires it pulls a thread from the thread pool and starts executing the job using that thread. When the job starts I request a UoW. StructureMap looks under the local storage for that thread to return the UoW, but because it can't find any it instantiates one and saves it under thread's local storage.I get the UoW, then perfom Begin, Commit, Dispose and everything is fine.
The problem happens when a thread is pulled from thread pool which was used before to fire a job (and used a UoW). Here when you request a UoW, StructureMap looks in the cache (thread local storage) and finds a UoW and returns it to you. But the problem is the UoW is disposed!
So we cannot really use UoW per thread for quartz jobs because the threads themselves are not disposed and they hold the old cached disposed UoWs. Basically life cycle of a thread does not match the life cycle of a quartz job. That's why I created my own life cycle for a quartz job.
First I created my own http-quartz hybrid life cycle class:
public class HybridHttpQuartzLifecycle : HttpLifecycleBase<HttpContextLifecycle, QuartzLifecycle>
{
public override string Scope { get { return "HybridHttpQuartzLifecycle"; } }
}
Then I created my QuartzLifecyle class:
public class QuartzLifecycle : ILifecycle
{
public void EjectAll()
{
FindCache().DisposeAndClear();
}
public IObjectCache FindCache()
{
return QuartzContext.Cache;
}
public string Scope { get { return "QuartzLifecycle"; } }
}
Then I need to create some context class like HttpContext for Quartz to hold the context related info. So I created QuartzContext class. When a quartz job is fired, the JobExecutionContext for that job should be registered in QuartzContext. Then the actual cache (MainObjectCache) for StructureMap instances will be created under that specific JobExecutionContext. So this way after the job execution finishes the cache will go away too and we won't have problem of disposed UoW in cache.
Also since _jobExecutionContext is ThreadStatic, when ever we request the cache from QuartzContext, it will return the cache from the JobExecutionContext that is saved for the same thread. So when multiple jobs are running at the same time, their JobExecutionContexts are saved separately and we will have separate caches for each running job.
public class QuartzContext
{
private static readonly string _cacheKey = "STRUCTUREMAP-INSTANCES";
[ThreadStatic]
private static JobExecutionContext _jobExecutionContext;
protected static void Register(JobExecutionContext jobExecutionContext)
{
_jobExecutionContext = jobExecutionContext;
_jobExecutionContext.Put(_cacheKey, new MainObjectCache());
}
public static IObjectCache Cache
{
get
{
return (IObjectCache)_jobExecutionContext.Get(_cacheKey);
}
}
}
I have an abstract class called BaseJobSingleSession that other jobs derive from. This class extends the QuartzContext class. You can see that I register the JobExecutionContext when the job is fired.
abstract class BaseJobSingleSession : QuartzContext, IStatefulJob
{
public override void Execute(JobExecutionContext context)
{
Register(context);
IUnitOfWork unitOfWork = ObjectFactory.GetInstance<IUnitOfWork>();
try
{
unitOfWork.Begin();
// do stuff ....
unitOfWork.Commit();
}
catch (Exception exception)
{
unitOfWork.RollBack();
}
finally
{
unitOfWork.Dispose();
}
}
}
Finally I defined the life cycle for UoW:
For<IUnitOfWork>().LifecycleIs(new HybridHttpQuartzLifecycle()).Use<UnitOfWork>();
(For life cycle and context classes I looked into the StructureMap source code to get the idea.)
Please share your ideas, comments and suggestions : >