I am using quartz and nhibernate and ran into a problem. Normally I have all my nhibernate sessions close on finish of a web request but I have a scheduler that starts on application start and I need to pass in a nhibernate session that I think should never be closed.
I am unsure how to do that.
Ninject
public class NhibernateSessionFactoryProvider : Provider<ISessionFactory>
{
protected override ISessionFactory CreateInstance(IContext context)
{
var sessionFactory = new NhibernateSessionFactory();
return sessionFactory.GetSessionFactory();
}
}
public class NhibernateModule : NinjectModule
{
public override void Load()
{
Bind<ISessionFactory>().ToProvider<NhibernateSessionFactoryProvider>().InSingletonScope();
Bind<ISession>().ToMethod(context => context.Kernel.Get<ISessionFactory>().OpenSession()).InRequestScope();
}
}
Global.aspx
protected void Application_Start()
{
// Hook our DI stuff when application starts
IKernel kernel = SetupDependencyInjection();
// get the reminder service HERE IS WHERE THE PROBLEMS START
IScheduledRemindersService scheduledRemindersService = kernel.Get<IScheduledRemindersService>();
scheduledRemindersService.StartTaskRemindersSchedule();
RegisterMaps.Register();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
public IKernel SetupDependencyInjection()
{
IKernel kernel = CreateKernel();
// Tell ASP.NET MVC 3 to use our Ninject DI Container
DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel));
return kernel;
}
protected IKernel CreateKernel()
{
var modules = new INinjectModule[]
{
new NhibernateModule(),
new ServiceModule(),
new RepoModule()
};
return new StandardKernel(modules);
}
// service that is causing me the problems. Ninject will bind reminderRepo and give it an nihbernate session.
private readonly IReminderRepo reminderRepo;
private readonly ISchedulerFactory schedulerFactory;
public ScheduledRemindersService(IReminderRepo reminderRepo)
{
this.reminderRepo = reminderRepo;
schedulerFactory = new StdSchedulerFactory();
}
public void StartTaskRemindersSchedule()
{
IScheduler scheduler = schedulerFactory.GetScheduler();
scheduler.Start();
JobDetail jobDetail = new JobDetail("TaskRemindersJob",null,typeof(TaskReminderJob));
jobDetail.JobDataMap["reminderRepo"] = reminderRepo;
DateTime evenMinuteDate = TriggerUtils.GetEvenMinuteDate(DateTime.UtcNow);
SimpleTrigger trigger = new SimpleTrigger("TaskRemindersTrigger", null,
DateTime.UtcNow,
null,
SimpleTrigger.RepeatIndefinitely,
TimeSpan.FromMinutes(1));
scheduler.ScheduleJob(jobDetail, trigger);
}
So I need to pass in the reminderRepo into the job as I am doing above
jobDetail.JobDataMap["reminderRepo"] = reminderRepo;
It's the only way you can pass something into a job. Everytime the schedule gets executed a job is recreated and I am assuming it uses the same reminderRepo that I sent in.
My code in the service layer never gets executed again and of course the application start as well(unless I redeploy the site)
Job
public class TaskReminderJob : IJob
{
public void Execute(JobExecutionContext context)
{
JobDataMap dataMap = context.JobDetail.JobDataMap;
ReminderRepo reminderRepo = dataMap["reminderRepo"] as ReminderRepo;
if (context.ScheduledFireTimeUtc.HasValue && context.NextFireTimeUtc.HasValue && reminderRepo != null)
{
DateTime start = context.ScheduledFireTimeUtc.Value;
DateTime end = context.NextFireTimeUtc.Value;
List<PersonalTaskReminder> personalTaskReminders = reminderRepo.GetPersonalTaskReminders(start, end);
if (personalTaskReminders.Count > 0)
{
reminderRepo.DeletePersonalTaskReminders(personalTaskReminders.Select(x => x.ReminderId).ToList());
}
}
}
Reminder Repo. (When this repo gets instantiated a session should be given that will live till the end of the request)
public class ReminderRepo : IReminderRepo
{
private readonly ISession session;
public ReminderRepo(ISession session)
{
this.session = session;
}
public List<PersonalTaskReminder> GetPersonalTaskReminders(DateTime start, DateTime end)
{
List<PersonalTaskReminder> personalTaskReminders = session.Query<PersonalTaskReminder>().Where(x => x.DateToBeSent <= start && x.DateToBeSent <= end).ToList();
return personalTaskReminders;
}
public void DeletePersonalTaskReminders(List<int> reminderId)
{
const string query = "DELETE FROM PersonalTaskReminder WHERE ReminderId IN (:reminderId)";
session.CreateQuery(query).SetParameterList("reminderId", reminderId).ExecuteUpdate();
}
public void Commit()
{
using (ITransaction transaction = session.BeginTransaction())
{
transaction.Commit();
}
}
}
So I need some way of keeping the session alive for my reminders. All my other sessions for all my other repos should be as I have it now. It's only this one that seems to need to live forever.
Edit
I tried to get a new session each time so I am passing the IsessionFactory around. Probably not 100% best but it was the only way I could figure out how to get some new sessions.
I however do not know if my session are being closed through ninject still since I am manually passing in the session now. I thinking now but cannot verify.
**private readonly ISessionFactory sessionFactory;**
private readonly ISchedulerFactory schedulerFactory;
public ScheduledRemindersService(ISessionFactory sessionFactory)
{
**this.sessionFactory = sessionFactory;**
schedulerFactory = new StdSchedulerFactory();
}
public void StartTaskRemindersSchedule()
{
IScheduler scheduler = schedulerFactory.GetScheduler();
scheduler.Start();
JobDetail jobDetail = new JobDetail("TaskRemindersJob",null,typeof(TaskReminderJob));
**jobDetail.JobDataMap["reminderRepo"] = sessionFactory;**
DateTime evenMinuteDate = TriggerUtils.GetEvenMinuteDate(DateTime.UtcNow);
SimpleTrigger trigger = new SimpleTrigger("TaskRemindersTrigger", null,
DateTime.UtcNow,
null,
SimpleTrigger.RepeatIndefinitely,
TimeSpan.FromMinutes(1));
scheduler.ScheduleJob(jobDetail, trigger);
}
So my global.aspx is the same but since ninject now sees that "ScheduledRemindersService" now takes in a nhibernate session factory it binds one for me that I can use.
I then pass it off to the job.
public void Execute(JobExecutionContext context)
{
JobDataMap dataMap = context.JobDetail.JobDataMap;
ISessionFactory sessionFactory = dataMap["reminderRepo"] as ISessionFactory;
if (sessionFactory != null)
{
ISession openSession = sessionFactory.OpenSession();
ReminderRepo reminderRepo = new ReminderRepo(openSession);
}
}
I then pass it into my ReminderRepo so I am guessing it ignores the auto session binding from ninject but I am not 100% sure thus I am not sure if my sessions are being closed.
Is there a reason the job can't just open up a new session every time it runs? Presumably it's running at periodic intervals and not forever ever.
Keeping stuff open forever is usually a sure-fire way to encounter weird behavior.
Hope this helps.
I think the approach to this is altogether wrong. The application scope of a web app is not the place to schedule events or try to persist "state" as it is still a website and the web server will not always have the app "started". This occurs after server restart and before app request, during app pool recycling and other misc cases like load balancing.
The best place to schedule something is either using a windows service or build a console app and then use windows scheduler to run it periodically. Any polling operations like that will cause major issues if you rely on application state.
I would also have serious concerns about the memory cost of letting a data context persist indefinitely even if you could count on it.
Check out building a console app - you'll find it's pretty easy even if you haven't done it before.
The other bonus is when you're sure of what you're doing, console apps can be switched to windows services with relative ease.
I've developed a similar solution recently.
I chose to use a "windows service" to manage my schedules.
Anyway, I don't understand why you do something like this:
ISessionFactory sessionFactory = dataMap["reminderRepo"] as ISessionFactory;
Since I had the same problems I've decided to (only in this situation) get a SessionFactory from StructureMap (that's what I've used) and open a new session by myself.
This is my job:
public class ReminderScheduleJob : IStatefulJob
{
private readonly ILogger _Logger;
private readonly ISessionFactory _SessionFactory;
public ReminderScheduleJob()
{
this._Logger = ObjectFactory.GetInstance<ILogger>();
this._SessionFactory = ObjectFactory.GetInstance<ISessionFactory>();
}
void IJob.Execute(JobExecutionContext context)
{
using (var session = this._SessionFactory.OpenSession())
{
using (var tx = session.BeginTransaction())
{
...
}
}
}
}
UPDATE:
This is my nHibernate registry (see structureMap for more infos) which is called at the start-up of my app (asp.net or windows service):
public class NhibernateRegistry: Registry
{
public NhibernateRegistry()
{
For<ISessionFactory>()
.Singleton()
.Use(new BpReminders.Data.NH.NHibernateSessionFactory(myConnectionString, schemaOperation).SessionFactory);
For<IUnitOfWork>()
.HybridHttpOrThreadLocalScoped()
.Use<BpReminders.Data.NH.UnitOfWork>();
For<ISession>()
.HybridHttpOrThreadLocalScoped()
.Use(o => ((BpReminders.Data.NH.UnitOfWork)o.GetInstance<IUnitOfWork>()).CurrentSession);
}
}
I've used a unit of work but that doesn't make any difference for you.
I've defined another registry (structureMap) for Quartz.net cause I want (and that's what they say) it to be singleton:
public class QuartzRegistry : Registry
{
public QuartzRegistry()
{
var properties = new NameValueCollection();
// I set all the properties here cause I persist my jobs etc on a DB.
For<ISchedulerFactory>()
.Singleton()
.Use(new StdSchedulerFactory(properties));
For<IScheduler>()
.Singleton()
.Use(x => x.GetInstance<ISchedulerFactory>().GetScheduler());
}
}
Now, in the global.asax I would start the scheduler asking StructureMap/ninject to resolve the IScheduler:
var scheduler = StructureMap.ObjectFactory.GetInstance<IScheduler>();
scheduler.Start();
I've seen you create a new scheduler in the ScheduledRemindersService:
public ScheduledRemindersService(IReminderRepo reminderRepo)
{
this.reminderRepo = reminderRepo;
schedulerFactory = new StdSchedulerFactory();
}
Since in my example the scheduler is already created and started as singleton you can ask ninject to have the instance ... and schedule your job.
In your job (TaskReminderJob) now you can ask the ObjectFactory to resolve the ISessionFactory (see the construct of my class ReminderScheduleJob).
You can now open a new session and pass the session to your repository.
When everything is done you can dispose your session.
Hope I've been clear enough.
As I told you I've got 2 layers. My web app is responsible to schedule the jobs and I've got a custom windows service (I haven't used the one available with Quartz.net) which is responsible to fetch the triggered events and do some actions.
Related
So,
Here is the code setup.
There is a driver application, which starts the HTTP server(ASP.NET core Web API project).
The method called by driver application for starting HTTP server is
this:
public class Http_Server
{
public static ConcurrentQueue<Object> cq = new ConcurrentQueue<Object>();
public static void InitHttpServer(ConcurrentQueue<Object> queue)
{
cq = queue;
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();
host.Run();
}
}
Controller Action Task:
[HttpPost]
[Route("XYZ")]
public virtual IActionResult AddXYZ([FromBody]List<Resourcemembers> resourcemembers)
{
//add something to ds
Http_Server.cq.Enqueue(new object());
//respond back
return new ObjectResult(example);
}
The data structure(a concurrent queue) being passed is to be made visible at controller level(like a global variable accessible across all controllers).
Is it fine to make the ds a static variable and access it across controllers?
Or Is there a way to pass this ds across to different layers?
This is my go at a better solution for this. What you are trying to do doesn't seem like the best way to approach this.
First, you want to enable caching in the application by calling the AddMemoryCache in the application StartUp.ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMemoryCache();
...
}
Then, you want to use the cache. Something like this should get you going in the right direction.
public class XYZController : Controller {
private IMemoryCache _memoryCache;
private const string xyzCacheKey = "XYZ";
public XYZController(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
[HttpPost("XYZ")]
public IActionResult AddXYZ([FromBody]ResourceMember[] resourceMembers)
{
try
{
if (!_memoryCache.TryGetValue(xyzCacheKey, out ConcurrentQueue<Object> xyz))
{
xyz = new ConcurrentQueue<Object>();
_memoryCache.Set(xyzCacheKey, xyz, new MemoryCacheEntryOptions()
{
SlidingExpiration = new TimeSpan(24, 0, 0)
});
}
xyz.Enqueue(resourceMembers);
return Ok();
}
catch (Exception ex)
{
return BadRequest(ex);
}
}
}
public class ResourceMember { }
What this does is allow you to use a Memory Cache to hold your object(s), and what ever you Enqueue in the ConcurrentQueue object, should you stay with that as your main object within the Cache. Now, you can cache any object type in the MemoryCache, and pull the value when you need to based on the key you gave it when you added it to the cache. In the case above, I created a const named xyzCacheKey with a string value of XYZ to use as the key.
That static global variable thing you are trying is just not... good.
If this doesn't help, let me know in a comment, I will delete the answer.
Good luck!
If I start my application and let it settle, it works great.
However, when I debug my application and if I close the browser tab before it initializes anything and then call another like localhost:81/Home/Test, it throws an exception on retrieving data from DB (EF).
This exception occurs during a call to a Filter CultureResolver which then calls LanguageService. Inside LanguageService there is a call to the DB to retrieve all the available languages.
I got many different exceptions, like:
The context cannot be used while the model is being created. This
exception may be thrown if the context is used inside the
OnModelCreating method or if the same context instance is accessed by
multiple threads concurrently. Note that instance members of
DbContext and related classes are not guaranteed to be thread safe.
Object reference not set to an instance of an object.
The underlying provider failed on Open.
Those exceptions occur all in the same query, it depends on how much time I left the first tab running.
So it seems it's something like Thread-Unsafe code or this query trying to get items before the Context is initialized.
I've the following:
SimpleInjectorInitializer.cs
public static class SimpleInjectorInitializer
{
/// <summary>Initialize the container and register it as MVC3 Dependency Resolver.</summary>
public static void Initialize()
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
InitializeContainer(container);
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.Verify();
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters, container);
}
private static void InitializeContainer(Container container)
{
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
/* Bindings... */
container.RegisterPerWebRequest<IAjaxMessagesFilter, AjaxMessagesFilter>();
container.RegisterPerWebRequest<ICustomErrorHandlerFilter, CustomErrorHandlerFilter>();
container.RegisterPerWebRequest<ICultureInitializerFilter, CultureInitializerFilter>();
}
}
FilterConfig.cs
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters, Container container)
{
filters.Add(container.GetInstance<ICultureInitializerFilter>());
filters.Add(container.GetInstance<ICustomErrorHandlerFilter>());
filters.Add(container.GetInstance<IAjaxMessagesFilter>());
}
}
CultureResolver.cs
public class CultureResolver : ICultureResolver
{
ILanguageService Service;
public CultureResolver(ILanguageService Service)
{
this.Service = Service;
}
public string Resolve(string CultureCode)
{
// Get the culture by name or code (pt / pt-pt)
ILanguageViewModel language = Service.GetByNameOrCode(CultureCode);
if (language == null)
{
// Get the default language
language = Service.GetDefault();
}
return language.Code;
}
}
LanguageService.cs
public class LanguageService : ILanguageService
{
IMembership membership;
ChatContext context;
ILanguageConverter converter;
public LanguageService(
ChatContext context,
IMembership membership,
ILanguageConverter converter
)
{
this.membership = membership;
this.context = context;
this.converter = converter;
}
public virtual ILanguageViewModel GetByNameOrCode(string Text)
{
string lowerText = Text.ToLower();
string lowerSmallCode = "";
int lowerTextHiphen = lowerText.IndexOf('-');
if (lowerTextHiphen > 0)
lowerSmallCode = lowerText.Substring(0, lowerTextHiphen);
Language item = this.context
.Languages
.FirstOrDefault(x => x.Code.ToLower() == lowerText
|| x.SmallCode.ToLower() == lowerText
|| x.SmallCode == lowerSmallCode);
return converter.Convert(item);
}
public virtual ILanguageViewModel GetDefault()
{
Language item = this.context
.Languages
.FirstOrDefault(x => x.Default);
return converter.Convert(item);
}
}
This is the query that is giving me the exceptions
Language item = this.context
.Languages
.FirstOrDefault(x => x.Code.ToLower() == lowerText
|| x.SmallCode.ToLower() == lowerText
|| x.SmallCode == lowerSmallCode);
Global filters in MVC and Web API are singletons. There is only one instance of such filter during the lifetime of your application. This becomes obvious when you look at the following code:
filters.Add(container.GetInstance<ICultureInitializerFilter>());
Here you resolve the filter once from the container and store it for the lifetime of the container.
You however, have registered this type as Scoped using:
container.RegisterPerWebRequest<ICultureInitializerFilter, CultureInitializerFilter>();
You are effectively saying that there should be one instance per web request, most likely because that class depends on a DbContext, which isn't thread-safe.
To allow your filters to have dependencies, you should either make them humble objects, or wrap them in a humble object that can call them. For instance, you can create the following action filter:
public sealed class GlobalActionFilter<TActionFilter> : IActionFilter
where TActionFilter : class, IActionFilter
{
private readonly Container container;
public GlobalActionFilter(Container container) { this.container = container; }
public void OnActionExecuted(ActionExecutedContext filterContext) {
container.GetInstance<TActionFilter>().OnActionExecuted(filterContext);
}
public void OnActionExecuting(ActionExecutingContext filterContext) {
container.GetInstance<TActionFilter>().OnActionExecuting(filterContext);
}
}
This class allows you to add your global filters as follows:
filters.Add(new GlobalActionFilter<ICultureInitializerFilter>(container));
filters.Add(new GlobalActionFilter<ICustomErrorHandlerFilter>(container));
filters.Add(new GlobalActionFilter<IAjaxMessagesFilter>(container));
The GlovalActionFilter<T> will callback into the container to resolve the supplied type every time it is called. This prevents the dependency from becoming captive which prevents the problems you are having.
I've read and Googled everything on this, but can't seem to get it to work. I created a custom LifetimeManager for Unity in my MVC5 application based on these posts:
MVC3 Unity Framework and Per Session Lifetime Manager
This may be the issue I am experiencing
Here is my SessionLifetimeManager
public class SessionLifetimeManager : LifetimeManager
{
private string key = Guid.NewGuid().ToString();
public override object GetValue()
{
return HttpContext.Current.Session[key];
}
public override void RemoveValue()
{
HttpContext.Current.Session.Remove(key);
}
public override void SetValue(object newValue)
{
HttpContext.Current.Session[key] = newValue;
}
}
I only have a few types I'm playing with, here is the relevant registrations in UnityConfig.cs:
container.RegisterType<IEpiSession, EpiSession>(new SessionLifetimeManager(),
new InjectionConstructor(config.AppServerURI, config.PathToSysConfig));
container.RegisterType<IReportRepository, EpicorReportRepository>(new TransientLifetimeManager());
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
Note that the EpicorReportRepository has a dependency on IEpiSession via constructor injection.
public class EpicorReportRepository : IReportRepository
{
private IEpiSession session;
// DI constructor
public EpicorReportRepository(IEpiSession session) {
this.session = session;
}
// ...
}
My Problem: After the first user / session connects to the application, every new user / session after that seems to still be using the EpiSession object and credentials that the first user had create/injected for him. This seems to be a common pattern used on the interwebs, so I'm wondering what I am missing.
How did you test that IEpiSession is the same in different Sessions?
Try to open you application from different browsers. If you open several tabs in the same browser then the same session is used.
I checked your code and it works for me.
There is the only one difference in SetResolver():
DependencyResolver.SetResolver(
type => container.Resolve(type),
types => container.ResolveAll(types));
The full registration code is the following:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
...
var container = new UnityContainer();
container.RegisterType<IEpiSession, EpiSession>(
new SessionLifetimeManager(),
new InjectionConstructor("config.AppServerURI", "config.PathToSysConfig"));
container.RegisterType<IReportRepository, EpicorReportRepository>(new TransientLifetimeManager());
DependencyResolver.SetResolver(
type => container.Resolve(type),
types => container.ResolveAll(types));
}
}
I am developing a web application to ASP.NET MVC. To interact with the data repository is used, based on nHibernate. Repositories are instantiated through Unity. To repository resolved ISession per request.
Now I need to add to the project crawlers performing periodic scheduled tasks in the background. To implement a crawler selected Quartz.NET. A project can have several different walkers, while interacting with the database through the repository, if matches the execution of their tasks.
The problem is that the crawler must create another session, because it is based on a request solution is not working (no web request). It was implemented a solution in which I register two different implementations of the ISession, but it does not work. The code samples below.
The following questions arise:
This is correct direction of solving the problem?
How to implement it correctly?
Will not this approach two crawler trying to write to the database, to cause mistake?
Code:
public class DatabaseRepositoriesRegistration : IUnityRegistration
{
public void Register(IUnityContainer container)
{
// web
var connectionString = WebConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
SessionManager.ConnectionString = connectionString;
container.RegisterType<ISession>(new HierarchicalLifetimeManager(),
new InjectionFactory(c => SessionManager.CurrentSession)
);
container.RegisterType<IUnitOfWork, NHibernateUnitOfWork>(new HierarchicalLifetimeManager());
container.RegisterType(typeof(IRepository<>), typeof(NHibernateRepository<>), new HierarchicalLifetimeManager());
// externals
container.RegisterType<ISession>("ext", new HierarchicalLifetimeManager(), new InjectionFactory(c =>
{
var config = Fluently.Configure().
Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(connectionString)
.UseReflectionOptimizer()
)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<MappingBeacon>())
.BuildConfiguration();
var sessionFactory = config.BuildSessionFactory();
return sessionFactory.OpenSession();
}));
container.RegisterType(typeof(IRepository<>), typeof(NHibernateRepository<>), "ext", new HierarchicalLifetimeManager(), new InjectionConstructor(new ResolvedParameter<ISession>("ext")));
}
}
public class SchedulerTask : MvcStartupTaskBase
{
private readonly IUnityContainer container;
public SchedulerTask(IUnityContainer container)
{
this.container = container;
}
public override void Run()
{
var scheduler = container.Resolve<IScheduler>();
scheduler.JobFactory = new UnityJobFactory(container);
scheduler.Start();
var job = JobBuilder.Create<UploadConvertionTask>().Build();
var trigger = TriggerBuilder.Create()
.WithDailyTimeIntervalSchedule
(s =>
s.WithIntervalInMinutes(1)
.OnEveryDay()
.StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(0, 0))
)
.Build();
scheduler.ScheduleJob(job, trigger);
}
public class UnityJobFactory : IJobFactory
{
private readonly IUnityContainer container;
public UnityJobFactory(IUnityContainer container)
{
this.container = container;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return (IJob)container.Resolve(bundle.JobDetail.JobType, "ext");
}
public void ReturnJob(IJob job)
{
}
}
}
public class UploadConvertionTask: IJob
{
public UploadConvertionTask(IRepository<Upload> uploadRepositiory) {
// for repository session is closed!
}
public void Execute(IJobExecutionContext context)
{
}
}
Your scheduler is set up as a local variable inside the run method. It needs to be a variable that lives around long enough for the scheduler to run the task. Without knowing the details of the whole project, you should set your scheduler to be a singleton that is created when the application starts. Then, reference this singleton instance when you're scheduling jobs.
Ideally, your scheduler should be run as a separate long-running service, such as a windows service.
I have implemented a service which uses a DAOFactory and a NHibernate Helper for the sessions and transactions. The following code is very much simplified:
public interface IService
{
IList<Disease> getDiseases();
}
public class Service : IService
{
private INHibernateHelper NHibernateHelper;
private IDAOFactory DAOFactory;
public Service(INHibernateHelper NHibernateHelper, IDAOFactory DAOFactory)
{
this.NHibernateHelper = NHibernateHelper;
this.DAOFactory = DAOFactory;
}
public IList<Disease> getDiseases()
{
return DAOFactory.getDiseaseDAO().FindAll();
}
}
public class NHibernateHelper : INHibernateHelper
{
private static ISessionFactory sessionFactory;
/// <summary>
/// SessionFactory is static because it is expensive to create and is therefore at application scope.
/// The property exists to provide 'instantiate on first use' behaviour.
/// </summary>
private static ISessionFactory SessionFactory
{
get
{
if (sessionFactory == null)
{
try
{
sessionFactory = new Configuration().Configure().AddAssembly("Bla").BuildSessionFactory();
}
catch (Exception e)
{
throw new Exception("NHibernate initialization failed.", e);
}
}
return sessionFactory;
}
}
public static ISession GetCurrentSession()
{
if (!CurrentSessionContext.HasBind(SessionFactory))
{
CurrentSessionContext.Bind(SessionFactory.OpenSession());
}
return SessionFactory.GetCurrentSession();
}
public static void DisposeSession()
{
var session = GetCurrentSession();
session.Close();
session.Dispose();
}
public static void BeginTransaction()
{
GetCurrentSession().BeginTransaction();
}
public static void CommitTransaction()
{
var session = GetCurrentSession();
if (session.Transaction.IsActive)
session.Transaction.Commit();
}
public static void RollbackTransaction()
{
var session = GetCurrentSession();
if (session.Transaction.IsActive)
session.Transaction.Rollback();
}
}
At the end of the day I just want to expose the IService to ASP.NET MVC/Console application/Winform. I can already use the Service in a console application but would like to improve it first. I guess the first improvement would be to inject the interfaces INHibernateHelper and IDAOFactory via castle. But I think the problem is that the NHibernateHelper might cause problems in a asp.net context where NHibernateHelper should run according to the 'Nhibernate session per request' pattern. One question I have is whether this pattern is determined by the nhibernate config section (setting current_session_context_class = web) or can i control this via castle somehow?
I hope this makes sense. The final aim is just to expose THE IService.
Thanks.
Christian
You have two choices..
1) Host it in WCF. This allows you access from any source you want.
2) Abstract away everything that's specific to how the code is being used. In our system for instance we use our own Unit Of Work implementation which is stored differently based on where the code is running. A small example would be storing something using the WCF call context vs. the current thread.