I was reviewing what I did to get StructureMap to work with UoW on Http and UoW on quartz, and I decided to share my solution here.
So the idea was that I wanted to use the StructureMap Hybrid scope to get an UoW instance when there is an http context, and also get a different UoW instance for each stream when there is no http context (for example, when a quartz job is triggered). Like this:
For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<UnitOfWork>();
UoW for http worked fine. The problem was in UoW for each thread.
This is what happens. When quratz fires, it pulls the thread from the thread pool and starts the job using that thread. When the task begins, I request UoW. StructureMap looks under local storage for this stream to return UoW, but due to the fact that it cannot find any instance of it, and stores it in the streaming local storage. I get UoW and then does Perom Begin, Commit, Dispose and everything is fine.
The problem occurs when a thread is pulled from a thread pool that was previously used to run a task (and used UoW). Here, when you request UoW, StructureMap looks into the cache (local thread storage) and finds UoW and returns it to you. But the problem is that UoW is configured!
Thus, we cannot use UoW for each thread for quartz jobs, because the threads themselves are not located, and they store the old cached UoW. Basically, the life cycle of a thread does not correspond to the life cycle of quartz. . Therefore, I created my own life cycle for quartz work.
First I created my own http-quartz hybrid lifecycle hybrid:
public class HybridHttpQuartzLifecycle : HttpLifecycleBase<HttpContextLifecycle, QuartzLifecycle> { public override string Scope { get { return "HybridHttpQuartzLifecycle"; } } }
Then I created the 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, such as the HttpContext for Quartz, to store the information related to the context. So I created the QuartzContext class. When a quartz job is running, the JobExecutionContext for this job must be registered in the QuartzContext. Then the actual cache (MainObjectCache) for the StructureMap instances will be created in this particular JobExecutionContext. Thus, after completing the task, the cache will also leave, and we will not have a problem with the UoW utility in the cache.
Also, since _jobExecutionContext is a ThreadStatic, when we request a cache from QuartzContext, it returns a cache from JobExecutionContext, which is stored for the same thread. Therefore, when several tasks are simultaneously running, their JobExecutionContexts are stored separately, and we will have separate caches for each job that is running.
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 from which other jobs come from. This class extends the QuartzContext class. You can see that I register JobExecutionContext when the job starts.
abstract class BaseJobSingleSession : QuartzContext, IStatefulJob { public override void Execute(JobExecutionContext context) { Register(context); IUnitOfWork unitOfWork = ObjectFactory.GetInstance<IUnitOfWork>(); try { unitOfWork.Begin();
Finally, I defined the life cycle for UoW:
For<IUnitOfWork>().LifecycleIs(new HybridHttpQuartzLifecycle()).Use<UnitOfWork>();
(For lifecycle and context classes, I looked at the source code of StructureMap to get this idea.)
Share your ideas, comments and suggestions:>