Hasbind 上的 MVC6 dnx451 Nhibernate nullreferenceexception

MVC6 dnx451 Nhibernate nullreferenceexception on hasbind

我正在使用 dnx451 将应用程序移动到 MVC 6。尝试在 nhibernate 中打开新会话时,我在检查当前上下文时收到 NullReferenceException。我正在使用 WebSessionContext(可以从堆栈跟踪中看到);但是看起来上下文没有成功存储在 HttpSession 中。

Nhibernate 目前可以与 MVC 6 一起使用吗?我目前在 MVC 5 中使用它。最大的不同是我如何获得会话。由于 MVC 6 不使用 HttpModules,我已将会话的打开和关闭移动到过滤器属性(我能看到的唯一缺点是如果在 veiw 中命中某些属性,可能会出现延迟加载异常)。

过滤代码如下:

public class DbTransactionAttribute:ActionFilterAttribute
    {
        private readonly IsolationLevel isolationLevel;

        /// <summary>
        /// Creates a transaction with IsolationLevel.ReadUncommitted
        /// </summary>
        public DbTransactionAttribute() {
            isolationLevel = IsolationLevel.ReadUncommitted;
        }

        public DbTransactionAttribute(IsolationLevel isolationLevel) {
            this.isolationLevel = isolationLevel;
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext) {
            SessionManager.Instance.OpenSession();
            SessionManager.Instance.Session.BeginTransaction(isolationLevel);
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext) {
            ITransaction transaction = SessionManager.Instance.Session.Transaction;
            if (transaction.IsActive) {
                if (filterContext.Exception != null && filterContext.ExceptionHandled)
                    transaction.Rollback();
                else transaction.Commit();
            }
            transaction.Dispose();
            SessionManager.Instance.DisposeCurrentSession(); // We are finished with the session
        }
}

启动方法:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection(key: "Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler(errorHandlingPath: "/Home/Error");
            }

            app.UseIISPlatformHandler();

            app.UseStaticFiles();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
            Nhibernate.Context.BuildContext(env);
            Nhibernate.SessionManager.BuildSessionManager(env);
        }

上下文:

class Context {
        private static Context instance;

        private ISessionFactory sessionFactory;

        internal ISessionFactory SessionFactory { get { return sessionFactory; } }

        internal static Context Instance {
            get {
                if (instance == null) Initialize();
                return instance;
            }
        }

        internal static void BuildContext(Microsoft.AspNet.Hosting.IHostingEnvironment env) {
            if(instance == null) Initialize();
        }

        private static void Initialize() {
            instance = new Context();
            var hbrConfig = new NHibernate.Cfg.Configuration();
            var files = typeof(Context).Assembly.GetManifestResourceNames();
            hbrConfig.Configure(typeof(Context).Assembly, 
                resourceName: "Ppn.Web.Nhibernate.hibernate.cfg.xml");
            hbrConfig.AddAssembly(typeof(ProposalNumber).Assembly);

            instance.sessionFactory = hbrConfig.BuildSessionFactory();
        }
    }

会话管理器:

public class SessionManager : ISessionManager {
        private static ISessionFactory sessionFactory;

        private static SessionManager instance;

        public static SessionManager Instance {
            get {
                return instance ?? (instance = new SessionManager(Context.Instance.SessionFactory));
            }
        }

        public static void BuildSessionManager(Microsoft.AspNet.Hosting.IHostingEnvironment env) {
            if (instance == null) instance = new SessionManager(Context.Instance.SessionFactory);
        }

        public SessionManager(ISessionFactory sessionFactory) {
            SessionManager.sessionFactory = sessionFactory;
        }

        public ISession Session {
            get {
                bool hasBind = CurrentSessionContext.HasBind(sessionFactory); //Line that fails
                ISession result;
                if (hasBind) result = sessionFactory.GetCurrentSession();
                else result = OpenSession();
                return result;
                //return CurrentSessionContext.HasBind(sessionFactory) ? sessionFactory.GetCurrentSession() : OpenSession();
            }
        }

        public ISession OpenSession() {
            ISession session = sessionFactory.OpenSession();
            CurrentSessionContext.Bind(session);
            return session;
        }

        public void DisposeCurrentSession() {
            if (CurrentSessionContext.HasBind(sessionFactory)) {
                ISession session = CurrentSessionContext.Unbind(sessionFactory);
                session.Close();
                session.Dispose();
            }
        }
    }

异常:

NullReferenceException: Object reference not set to an instance of an object.

    lambda_method(Closure , Object )
    NHibernate.Context.ReflectiveHttpContext.get_HttpContextCurrentItems()
    NHibernate.Context.WebSessionContext.GetMap()
    NHibernate.Context.MapBasedSessionContext.get_Session()
    NHibernate.Context.CurrentSessionContext.HasBind(ISessionFactory factory)
    Ppn.Web.Nhibernate.SessionManager.get_Session() in SessionManager.cs
                        bool hasBind = CurrentSessionContext.HasBind(sessionFactory);
    Ppn.Web.Controllers.ProposalNumberController.Index() in ProposalNumberController.cs
                    ISession dbSession = SessionManager.Instance.Session;
    --- End of stack trace from previous location where exception was thrown ---

This 告诉我 NHibernate 正在使用反射来访问 属性。 Asp.Net.Core 是一个重大的重写,有很多破坏性的变化,因此 Asp.Net Core 和以前的版本之间没有确切的 binary/API/functional 对等。因此,许多使用以前版本的 Asp.Net 设计的项目将无法使用 Asp.Net.Core。

我找到了创建自定义 ICurrentSessionContext 的解决方案,如下所示:

[Serializable]
public class Mvc6SessionContext: MapBasedSessionContext {
    private const string SessionFactoryMapKey = "NHibernate.Context.WebSessionContext.SessionFactoryMapKey";

    public Mvc6SessionContext(ISessionFactoryImplementor factory) : base(factory) {}

    protected override IDictionary GetMap() {
        return Context.Instance.HttpContext.Items[SessionFactoryMapKey] as IDictionary;
    }

    protected override void SetMap(IDictionary value) {
        Context.Instance.HttpContext.Items[SessionFactoryMapKey] = value;
    }
}

我们仍然需要获得对上下文的自定义访问权限。因此,我刚刚修改了我的应用程序的上下文项,如下所示:

class Context {
    private static Context instance;

    private ISessionFactory sessionFactory;
    private static IHttpContextAccessor contextAccessor;

    internal ISessionFactory SessionFactory { get { return sessionFactory; } }

    internal static Context Instance {
        get {
            if (instance == null) Initialize();
            return instance;
        }
    }

    internal HttpContext HttpContext {
        get { return contextAccessor.HttpContext; }
    }

    internal static void BuildContext(IApplicationBuilder app) {
        if(contextAccessor == null) contextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
        if (instance == null) Initialize();
    }

    private static void Initialize() {
        instance = new Context();
        var hbrConfig = new NHibernate.Cfg.Configuration();
        var files = typeof(Context).Assembly.GetManifestResourceNames();
        hbrConfig.Configure(typeof(Context).Assembly, 
            resourceName: "Ppn.Web.Nhibernate.hibernate.cfg.xml");
        hbrConfig.AddAssembly(typeof(ProposalNumber).Assembly);
        instance.sessionFactory = hbrConfig.BuildSessionFactory();
    }
}

并且在启动时我通过 IApplicationBuilder

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection(key: "Logging"));
        loggerFactory.AddDebug();

        if (env.IsDevelopment())
        {
            app.UseBrowserLink();
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler(errorHandlingPath: "/Home/Error");
        }

        app.UseIISPlatformHandler();

        app.UseStaticFiles();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
        Nhibernate.Context.BuildContext(app);
        Nhibernate.SessionManager.BuildSessionManager(env);
    }

最后一件事是告诉 Nhibernate 在配置文件中使用我的实现:

<property name="current_session_context_class">Ppn.Web.Nhibernate.Mvc6SessionContext, Ppn.Web</property>

整个 thig 的语法应该与 Nhibernate 中使用的标准 "web" 键相同。我已经测试过,到目前为止一切顺利。

需要说明的是,这个项目是 dnx451 而不是 .net 核心(由于很多原因它不会工作)