使用Autofac进行Session-per-HttpRequest。当不通过DependencyResolver访问ISession时,NHProf显示“多会话”警告

时间:2014-03-03 03:00:57

标签: c# session nhibernate autofac

我已经使用Autofac成功实现了Session-per-HttpRequest。

我对我的实施不满意,因为我正在经历DependencyResolver并且不依赖于AutoFac提供的参数。如果我依赖AutoFac提供的ISession参数,那么我会收到NHProf的警告,表明正在使用多个Sessions。如果我通过DependencyResolver,NHProf的警告就会消失,但使用方法对我来说不合适。

我遵循了此处概述的Autofac + MVC4.0指南:https://code.google.com/p/autofac/wiki/MvcIntegration

我也使用本指南作为参考。它表示应该可以接受ISession作为构造函数参数:http://slynetblog.blogspot.com/2011/04/lightweight-nhibernate-and-aspnet-mvc.html

以下是我构建Autofac容器的方法:

public class AutofacRegistrations
{
    public static void RegisterAndSetResolver()
    {
        var containerBuilder = new ContainerBuilder();

        containerBuilder.RegisterControllers(Assembly.GetExecutingAssembly());

        //  Only generate one SessionFactory ever because it is expensive.
        containerBuilder.Register(x => new NHibernateConfiguration().Configure().BuildSessionFactory()).SingleInstance();

        //  Everything else wants an instance of Session per HTTP request, so indicate that:
        containerBuilder.Register(x => x.Resolve<ISessionFactory>().OpenSession()).As<ISession>().InstancePerHttpRequest();
        containerBuilder.Register(x => LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType)).As<ILog>().InstancePerHttpRequest();

        containerBuilder.RegisterType<NHibernateDaoFactory>().As<IDaoFactory>().InstancePerHttpRequest();
        containerBuilder.RegisterType<StreamusManagerFactory>().As<IManagerFactory>().InstancePerHttpRequest();

        //  containerBuilder.RegisterModule adds all the required http modules to support per web request lifestyle and change default controller factory to the one that uses Autofac.
        containerBuilder.RegisterModule(new AutofacWebTypesModule());

        IContainer container = containerBuilder.Build();
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
    }
}

这是我的基本控制器类。请注意注释掉的代码,原始接受的会话作为参数:

public abstract class StreamusController : Controller
{
    protected readonly ILog Logger;
    protected new readonly ISession Session;

    protected StreamusController(ILog logger, /*ISession session*/)
    {
        if (logger == null) throw new ArgumentNullException("logger");
        //if (session == null) throw new ArgumentNullException("session");

        Logger = logger;

        //  TODO: Is this different than passing ISession into Controller with AutoFac?
        Session = DependencyResolver.Current.GetService<ISession>();
        //Session = session;
    }

}

根据我是使用ISession作为参数还是通过DependencyResolver访问它,我在NHProf中遇到了不同的结果。为什么?我的理解是这两种方式应该完全一样!

供参考,这是我的Lazy NHibernateConfiguration / ISessionFactory实现。我认为这与手头的问题无关:

public class NHibernateConfiguration
{
    public FluentConfiguration Configure()
    {
        string connectionString = ConfigurationManager.ConnectionStrings["default"].ConnectionString;

        FluentConfiguration fluentConfiguration = Fluently.Configure()
            .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString).ShowSql().FormatSql())
            .Mappings(cfg => cfg.FluentMappings.AddFromAssemblyOf<UserMapping>())
            .ExposeConfiguration(ConfigureStreamusDataAccess);

        return fluentConfiguration;
    }

    private static void ConfigureStreamusDataAccess(Configuration configuration)
    {
        //  NHibernate.Context.WebSessionContext - analogous to ManagedWebSessionContext above, stores the current session in HttpContext. 
        //  You are responsible to bind and unbind an ISession instance with static methods of class CurrentSessionContext.
        configuration.SetProperty("current_session_context_class", "web");
        configuration.SetProperty("connection.isolation", "ReadUncommitted");
        configuration.SetProperty("default_schema", "[Streamus].[dbo]");
        configuration.SetProperty("generate_statistics", "true");
    }
}

以下是NHProf的屏幕截图,指示我的CreateMultiple操作上的多个会话,以及另一个不指示多个会话的屏幕截图。第一个屏幕截图使用传入的ISession作为参数,第二个屏幕截图使用DependencyResolver:

enter image description here enter image description here

2 个答案:

答案 0 :(得分:1)

我不确定为什么会这样,但你可以这样写注册:

containerBuilder.Register(x => {
    return x.Resolve<ISessionFactory>().OpenSession(); //set breakpoint here
}).As<ISession>().InstancePerHttpRequest();

并在OpenSession()调用上设置断点,然后通过代码调试,看看每次调用时调用堆栈的样子。

答案 1 :(得分:0)

好吧,所以我追查了罪魁祸首。这并不是很明显。

我使用AutoMapper将DTO映射到域并返回。这显然是bad practice

我的逻辑看起来像:

  • 的Application_Start
  • AutofacRegistrations.RegisterAndSetResolver
  • AutoMapper.SetMappings

在SetMappings内部,我需要访问我的DAO工厂才能将我的DTO映射回域:AutoMapper best practices - Should I be asking the DAO for information to fulfill mapping from DTO to domain object?

在DependencyResolver中询问我的DaoFactory。这就是问题出现的地方。在Autofac有机会为我当前的请求创建一个会话之前,我正在要求一个DaoFactory(因为我还没有请求。)这导致它过早地生成一个ISession,以便能够淘汰DaoFactory。

重构我的代码,使每个Domain对象负责重新映射解决问题。类似的东西:

public static PlaylistItem Create(PlaylistItemDto playlistItemDto, IPlaylistManager playlistManager)
{
    PlaylistItem playlistItem = new PlaylistItem
        {
            Cid = playlistItemDto.Cid,
            Id = playlistItemDto.Id,
            Playlist = playlistManager.Get(playlistItemDto.PlaylistId),
            Sequence = playlistItemDto.Sequence,
            Title = playlistItemDto.Title,
            Video = Video.Create(playlistItemDto.Video)
        };

    return playlistItem;
}

我使用IoC将IPlaylistManager提供给PlaylistItem,而不是通过DependencyResolver访问它。