Web 中的 NHibernate API ASP.NET: 没有会话绑定到当前上下文

NHibernate in Web API ASP.NET: No session bound to the current context

我是 NHibernate 的新手,正在尝试在 ASP.NET WEB API 中使用它。首先,我成功地使用了一个名为 "Category" 的 table,控制器 class 如下所示:

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;

namespace TestMVCProject.Web.Api.Controllers
{
    [LoggingNHibernateSession]
    public class CategoryController : ApiController
    {
        private readonly ISession _session;
        private readonly ICategoryMapper _categoryMapper;
        private readonly IHttpCategoryFetcher _categoryFetcher;

        public CategoryController(
            ISession session,
            ICategoryMapper categoryMapper,
            IHttpCategoryFetcher categoryFetcher)
        {
            _session = session;
            _categoryMapper = categoryMapper;
            _categoryFetcher = categoryFetcher;
        }

        public IEnumerable<Category> Get()
        {
            return _session
                .QueryOver<Data.Model.Category>()
                .List()
                .Select(_categoryMapper.CreateCategory)
                .ToList();
        }

        public Category Get(long id)
        {
            var category = _categoryFetcher.GetCategory(id);
            return _categoryMapper.CreateCategory(category);
        }


        public HttpResponseMessage Post(HttpRequestMessage request, Category category)
        {
            var modelCategory = new Data.Model.Category
            {
                Description = category.Description,
                CategoryName = category.CategoryName
            };

            _session.Save(modelCategory);

            var newCategory = _categoryMapper.CreateCategory(modelCategory);

            //var href = newCategory.Links.First(x => x.Rel == "self").Href;

            var response = request.CreateResponse(HttpStatusCode.Created, newCategory);
            //response.Headers.Add("Location", href);

            return response;
        }


        public HttpResponseMessage Delete()
        {
            var categories = _session.QueryOver<Data.Model.Category>().List();
            foreach (var category in categories)
            {
                _session.Delete(category);
            }

            return new HttpResponseMessage(HttpStatusCode.OK);
        }


        public HttpResponseMessage Delete(long id)
        {
            var category = _session.Get<Data.Model.Category>(id);
            if (category != null)
            {
                _session.Delete(category);
            }

            return new HttpResponseMessage(HttpStatusCode.OK);
        }


        public Category Put(long id, Category category)
        {
            var modelCateogry = _categoryFetcher.GetCategory(id);

            modelCateogry.CategoryName = category.CategoryName;
            modelCateogry.Description = category.Description;

            _session.SaveOrUpdate(modelCateogry);

            return _categoryMapper.CreateCategory(modelCateogry);
        }
    }
}

但是当我添加具有类别 table 的外键的 "Product" table 时,产品控制器不起作用并抛出以下异常:

No session bound to the current context

ProductController class如下:

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;

namespace TestMVCProject.Web.Api.Controllers
{
    [LoggingNHibernateSession]
    public class ProductController : ApiController
    {
        private readonly ISession _session;
        private readonly IProductMapper _productMapper;
        private readonly IHttpProductFetcher _productFetcher;


        public ProductController(
            ISession session,
            IProductMapper productMapper,
            IHttpProductFetcher productFetcher)
        {
            _session = session;
            _productMapper = productMapper;
            _productFetcher = productFetcher;
        }

        public IEnumerable<Product> Get()
        {
            return _session
                .QueryOver<Data.Model.Product>()
                .List()
                .Select(_productMapper.CreateProduct)
                .ToList();
        }

        public Product Get(long id)
        {
            var product = _productFetcher.GetProduct(id);
            return _productMapper.CreateProduct(product);
        }


        public HttpResponseMessage Post(HttpRequestMessage request, Product product)
        {
            var modelProduct = new Data.Model.Product
            {
                Description = product.Description,
                ProductName = product.ProductName
            };

            _session.Save(modelProduct);

            var newProduct = _productMapper.CreateProduct(modelProduct);

            //var href = newproduct.Links.First(x => x.Rel == "self").Href;

            var response = request.CreateResponse(HttpStatusCode.Created, newProduct);
            //response.Headers.Add("Location", href);

            return response;
        }


        public HttpResponseMessage Delete()
        {
            var categories = _session.QueryOver<Data.Model.Product>().List();
            foreach (var product in categories)
            {
                _session.Delete(product);
            }

            return new HttpResponseMessage(HttpStatusCode.OK);
        }


        public HttpResponseMessage Delete(long id)
        {
            var product = _session.Get<Data.Model.Product>(id);
            if (product != null)
            {
                _session.Delete(product);
            }

            return new HttpResponseMessage(HttpStatusCode.OK);
        }


        public Product Put(long id, Product product)
        {
            var modelProduct = _productFetcher.GetProduct(id);

            modelProduct.ProductName = product.ProductName;
            modelProduct.Description = product.Description;

            _session.SaveOrUpdate(modelProduct);

            return _productMapper.CreateProduct(modelProduct);
        }
    }
}

和产品 table 的映射 class:

using TestMVCProject.Data.Model;
using FluentNHibernate.Mapping;

namespace TestMVCProject.Data.SqlServer.Mapping
{
    public class ProductMap : ClassMap<Product>
    {
        public ProductMap()
        {
            Id(x => x.ProductId);
            Map(x => x.ProductName).Not.Nullable();
            Map(x => x.Description).Nullable();
            Map(x => x.CreateDate).Not.Nullable();
            Map(x => x.Price).Not.Nullable();               

            References<Category>(x => x.CategoryId).Not.Nullable();               
        }
    }
}

怎么了?

您的代码片段没有说明 ISessionFactory 是如何创建的以及 ISession 是如何传递到您的控制器中的……您应该了解这个非常全面的故事 (作者彼得·瓦拉特):

NHibernate session management in ASP.NET Web API

哪里可以看到我们,可以用2.3. Contextual Sessions:

NHibernate.Context.WebSessionContext - stores the current session in HttpContext. You are responsible to bind and unbind an ISession instance with static methods of class CurrentSessionContext.

配置

<session-factory>
    ..
    <property name="current_session_context_class">web</property>
</session-factory>

在文章中您可以检查我们是否需要在应用启动时初始化工厂(只是摘录):

public class WebApiApplication : System.Web.HttpApplication  
{
    private void InitializeSessionFactory() { ... }

    protected void Application_Start()
    {
        InitializeSessionFactory();
    ...

接下来我们应该创建一些 AOP 过滤器(只是一个摘录):

public class NhSessionManagementAttribute : ActionFilterAttribute  
{
    ...
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        // init session
        var session = SessionFactory.OpenSession();
        ...

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        // close session
        ...
        session = CurrentSessionContext.Unbind(SessionFactory);
    }

有关详细信息,请查看 source mentioned above

您将会话传递给控制器​​工厂的构造函数的方法似乎不起作用,有几种方法可以做到这一点

1.使用依赖注入

如果您使用的是依赖注入框架,则必须配置控制器,以便根据请求构建它,它应该看起来像这样(我已经使用了 Ninject 的代码)

步骤 1 - 设置注入会话

public class DIModule : NinjectModule
{
    public override void Load()
    {
    this.Bind<ISessionFactory>()... bind to the session factory
        this.Bind<ISession>().ToMethod(ctx => ctx.Kernel.Get<ISessionFactory>().OpenSession())
            .InRequestScope();
    }

    private ISession CreateSessionProxy(IContext ctx)
    {
        var session = (ISession)this.proxyGenerator.CreateInterfaceProxyWithoutTarget(typeof(ISession), new[] { typeof(ISessionImplementor) }, ctx.Kernel.Get<SessionInterceptor>());
        return session;
    }
}

步骤 2 - 创建控制器工厂,以便在解析时注入会话

public class NinjectControllerFactory : DefaultControllerFactory, IDependencyResolver
    {
        private IDependencyResolver _defaultResolver;

        public NinjectControllerFactory(IDependencyResolver defaultResolver)
        {
            _defaultResolver = defaultResolver;
        }

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            return controllerType == null
                   ? null
                   : (IController)DependencyKernel.Kernel.Get(controllerType);
        }

        public IDependencyScope BeginScope()
        {
            return this;
        }

        public object GetService(Type serviceType)
        {
            try
            {
                return DependencyKernel.Kernel.Get(serviceType);
            }
            catch (Exception)
            {
                return GetService(serviceType);
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try
            {
                object item = DependencyKernel.Kernel.Get(serviceType);
                return new List<object>() {item};
            }
            catch (Exception)
            {
                return GetServices(serviceType);
            }
        }

        public void Dispose()
        {
        }
    }

步骤 3 - 注册控制器工厂

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {

            var factory = new NinjectControllerFactory(GlobalConfiguration.Configuration.DependencyResolver);
            ControllerBuilder.Current.SetControllerFactory(factory);
            GlobalConfiguration.Configuration.DependencyResolver = factory;
        }
    }

现在会发生的是,当您的控制器被创建时,它将为每个请求注入一个新的 NH 会话。

2。使用过滤器

这要简单得多,但您可能需要稍微更改一下控制器才能工作,

步骤 1 - 为工厂设置正确的会话上下文

_sessionFactory = CreateConfiguration()
                .ExposeConfiguration(c => c.SetProperty("current_session_context_class","web"))
                .BuildSessionFactory();

步骤 2 - 创建过滤器

public class SessionPerRequestAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var session = SessionFactory.OpenSession();
            NHibernate.Context.CurrentSessionContext.Bind(session);
            base.OnActionExecuting(actionContext);
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            var session = SessionFactory.GetCurrentSession();
            session.Flush();
            session.Clear();
            session.Close();
            base.OnActionExecuted(actionExecutedContext);
        }
    }

步骤 3 - 在全局配置中注册过滤器

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            //Do other config here
            config.Filters.Add(new SessionPerRequestAttribute());
        }
    }

第 4 步 - 稍微修改一下控制器,

public class CategoryController : ApiController
{
    private readonly ICategoryMapper _categoryMapper;
    private readonly IHttpCategoryFetcher _categoryFetcher;

    public CategoryController(
        ICategoryMapper categoryMapper,
        IHttpCategoryFetcher categoryFetcher)
    {
        _categoryMapper = categoryMapper;
        _categoryFetcher = categoryFetcher;
    }

    public IEnumerable<Category> Get()
    {
        var session = SessionFactory.GetCurrentSession();
        return session 
            .QueryOver<Data.Model.Category>()
            .List()
            .Select(_categoryMapper.CreateCategory)
            .ToList();
    }
}

这里发生的事情是,当一个请求到来时,它会创建一个新的会话,它被绑定到请求上下文,同样用于网络 API 方法。