【问题标题】:Keep an nihbernate session open forever?保持 nihbernate 会话永远打开?
【发布时间】:2013-08-07 05:00:12
【问题描述】:

我正在使用quartz 和nhibernate,但遇到了问题。通常我会在网络请求完成时关闭所有的休眠会话,但我有一个在应用程序启动时启动的调度程序,我需要传入一个我认为永远不应该关闭的休眠会话。

我不确定该怎么做。

忍者

 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();
        }
    }

全球.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);
    }

// 导致我出现问题的服务。 Ninject 将绑定reminderRepo 并给它一个nihbernate 会话。

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);
}

所以我需要像上面那样将reminderRepo传递给工作

jobDetail.JobDataMap["reminderRepo"] = reminderRepo;

这是您将某些东西传递给工作的唯一方法。每次执行计划时,都会重新创建一个作业,我假设它使用与我发送的相同的reminderRepo。

我在服务层中的代码永远不会再次执行,当然应用程序也会启动(除非我重新部署站点)

工作

 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());


                }

            }
        }

提醒回购。 (当这个 repo 被实例化时,应该给出一个会话,该会话将一直持续到请求结束)

  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();
            }
        }


    }

所以我需要一些方法来让会话保持活跃以进行提醒。我所有其他回购的所有其他会话都应该像我现在拥有的那样。似乎只有这一个需要永生。

编辑

我每次都尝试获取一个新会话,因此我传递了 IsessionFactory。可能不是 100% 最好,但这是我弄清楚如何获得一些新会话的唯一方法。

但是我不知道我的会话是否仍然通过 ninject 关闭,因为我现在手动传递会话。我现在想,但无法验证。

 **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);
}

所以我的 global.aspx 是相同的,但是由于 ninject 现在看到“ScheduledRemindersService”现在接受了一个休眠会话工厂,它为我绑定了一个我可以使用的工厂。

然后我把它传递给工作。

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);
    }
}

然后我将它传递到我的 ReminderRepo 中,所以我猜它会忽略来自 ninject 的自动会话绑定,但我不能 100% 确定,因此我不确定我的会话是否正在关闭。

【问题讨论】:

    标签: asp.net-mvc nhibernate quartz.net


    【解决方案1】:

    是否有原因该作业不能在每次运行时都打开一个新会话?大概它会定期运行,而不是永远运行。

    永远保持开放通常是遇到奇怪行为的可靠方法。

    希望这会有所帮助。

    【讨论】:

    • 如果我知道我可能会选择这个,但我所拥有的是你可以传递一些东西并且每次都重新创建工作的唯一方法。因此,无论是在 void Execute(JobExecutionContext context) 中,我都需要在那里创建一个新会话,但我不知道如何。
    • 好吧,我想我可能已经完成了你所说的创建一个新会话,但是我不知道会话是否自动关闭,也不知道如何测试它。我认为不是,但不能确定。见编辑
    • 出于测试目的,您可以将注入的会话包装在您自己的实现相同接口的对象中。然后,您可以将您关心的所有调用委托给被包装的对象,然后预先记录消息以确保正确调用会话关闭。 (这是装饰者模式)。
    【解决方案2】:

    我认为解决这个问题的方法是完全错误的。 Web 应用程序的应用范围不是安排事件或尝试保持“状态”的地方,因为它仍然是一个网站,并且 Web 服务器不会总是让应用程序“启动”。这发生在服务器重启之后和应用请求之前,在应用池回收和其他诸如负载平衡之类的其他情况下。

    安排某事的最佳位置是使用 Windows 服务或构建控制台应用程序,然后使用 Windows 调度程序定期运行它。如果您依赖应用程序状态,任何类似的轮询操作都会导致重大问题。

    我也非常担心让数据上下文无限期持续存在的内存成本,即使您可以指望它。

    查看构建控制台应用程序 - 即使您以前没有做过,也会发现它非常简单。

    另一个好处是,当您确定自己在做什么时,控制台应用程序可以相对轻松地切换到 Windows 服务。

    【讨论】:

    • 是的,控制台应用程序很好并且易于制作,但我没有钱将我的网站托管在可以运行控制台应用程序或 Windows 调度程序的专用服务器上。我必须得到应有的回报,那就是共享主机。
    • 您是否考虑过在 SQL Server 上使用计划操作?您可以使用客户端脚本检查条目并在读取时删除,然后只需一个运行更新的代理作业。我想你真的会发现它在 App 状态下无法正常工作。在 App_Start 中实例化的对象稍后将被清空。当另一个进程负责垃圾收集时,有状态对象不能很好地持久化。
    • 如果您必须在应用程序状态下执行此操作,那么无论您使用什么,您都必须检查 null 状态和/或打开状态,因为我 100% 确定您会找到会话为空,而应用程序不一定有时会重新启动。
    【解决方案3】:

    我最近开发了一个类似的解决方案。
    我选择使用“Windows 服务”来管理我的日程安排。
    无论如何,我不明白你为什么要这样做:

    ISessionFactory sessionFactory = dataMap["reminderRepo"] as ISessionFactory;
    

    由于我遇到了同样的问题,我决定(仅在这种情况下)从 StructureMap 中获取一个 SessionFactory(这是我使用的)并自己打开一个新会话。

    这是我的工作:

    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())
                    {
                    ...
                    }
                }
    
            }
        }
    

    更新:

    这是我的 nHibernate 注册表(有关更多信息,请参阅 structureMap),它在我的应用程序(asp.net 或 Windows 服务)启动时调用:

    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);
        }
    }
    

    我使用了一个工作单元,但这对你没有任何影响。
    我已经为 Quartz.net 定义了另一个注册表(structureMap),因为我想要(这就是他们所说的)它是单例的:

    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());
        }
    }
    

    现在,在 global.asax 中,我将启动调度程序,要求 StructureMap/ninject 解析 IScheduler:

    var scheduler = StructureMap.ObjectFactory.GetInstance<IScheduler>();
    scheduler.Start();
    

    我看到你在 ScheduledRemindersService 中创建了一个新的调度程序:

    public ScheduledRemindersService(IReminderRepo reminderRepo)
    {
        this.reminderRepo = reminderRepo;
        schedulerFactory = new StdSchedulerFactory();
    }
    

    由于在我的示例中,调度程序已经创建并作为单例启动,您可以要求 ninject 拥有该实例......并安排您的工作。
    现在,在您的工作 (TaskReminderJob) 中,您可以要求 ObjectFactory 解析 ISessionFactory(请参阅我的类 ReminderScheduleJob 的构造)。 您现在可以打开一个新会话并将该会话传递到您的存储库。
    当一切都完成后,你可以处理你的会话。 希望我已经足够清楚了。
    正如我告诉你的,我有 2 层。我的网络应用程序负责安排作业,并且我有一个自定义 Windows 服务(我没有使用 Quartz.net 提供的服务),它负责获取触发的事件并执行一些操作。

    【讨论】:

    • 我这样做是因为这是我弄清楚如何获得新会话的唯一方法。可以告诉我你的 ObjectFactory 是什么样子的吗?还有我做的方式有什么问题吗?
    • @chobo2:我做的和你做的一模一样。在我的 global.asax 中,我已经引导了我的 DI。我的 SessionFactory 是单例的,我的会话是 HttpContextScoped。无论如何,我会尝试在我之前的答案中添加更多信息。
    猜你喜欢
    • 2010-12-18
    • 1970-01-01
    • 2017-03-12
    • 1970-01-01
    • 2020-09-17
    • 1970-01-01
    • 2010-11-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多