3

我正在使用带有 mssql db 的 mvc3 nhibernate orm 层开发我的第一个应用程序。

这是我使用 nhibernate 创建的第一个应用程序,除了初始响应时间之外,一切都很好。经过一些调查,我实现了每个 Web 请求的会话,这绝对是一次升级,我的实体在第一次调用后加载速度更快,但我的问题仍然存在。

初始响应时间真的很慢,当我输入 domainname.com 并点击输入 lwaiting 时间大约是。10-15 秒。这不是内容的实际加载时间,在 10-15 秒之后。我的网站开始加载,还有几秒钟。

那个时候会话工厂必须初始化所有需要的“东西”,但我认为它必须是别的东西。这是无法接受的。

我的应用程序在站点内存分配 200 MB 的 winhost 上运行,所以我认为这不是问题。

欢迎任何提示。如果您需要更多详细信息,请询问。

谢谢

更新: 在使用 nhibernate profiler 检查应用程序会话使用情况后,我发现了一些有趣的东西。因为我真的是使用分析器的初学者,所以我认为我发现了昂贵的会话。在一般统计中,67 个实体在 36.571 秒内加载。这个秒值真的很奇怪,因为我要加载 10-max 15 秒。

第二次更新: global.asax

public class MvcApplication : System.Web.HttpApplication{     

public static ISessionFactory SessionFactory =
        MyDomain.Infrastructure.SessionProvider.CreateSessionFactory();

//My session factory is open in Application_Start() like this 
SessionFactory.OpenSession();

}

我正在使用流利的方法来映射我的对象。所以我在域项目中的会话提供者看起来像这样

//This should be used from web app, global.asax.cs calls
        public static ISessionFactory CreateSessionFactory()
        {
            string conStringName = "ConnectionString";
            var cfg = Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2008
                .ConnectionString(c => c.FromConnectionStringWithKey(conStringName)))
                .Mappings(m => m.FluentMappings.Add<Entity1>())
                .Mappings(m => m.FluentMappings.Add<Entity2>())
                .Mappings(m => m.FluentMappings.Add<Entity3>())
                .ExposeConfiguration(p => p.SetProperty("current_session_context_class", "web"))
                .BuildConfiguration();

            return cfg.BuildSessionFactory();            
        }

更新 3 仍然没有解决这个问题

更新 4 和最终 我的问题肯定在 sessionFactory 中。我认为我的配置对象应该被序列化。如果有人可以使用我的代码显示如何使用此处显示的流利的conf来展示如何做到这一点。我很乐意接受他/她的回答。谢谢。

4

3 回答 3

12

缺少很多细节,但既然你说这是你的第一个 NHibernate 应用程序,我将推荐可能需要检查的内容:

  • 在应用程序启动时创建一次 NH SessionFactory (正如@Rippo 所做的那样)。创建SessionFactory 的成本很高。在 Application_Start() 中执行
  • 为每个 Web 请求打开一个新的 NH 会话。一旦请求结束,就把它扔掉。NH ISession 便宜/快速创建。通常,长时间重用或缓存 ISession 是不好的做法。对于一个简单的实现,如果你愿意,你可以在你的 Controller 中完成它,因为它只存在于每个请求中。
  • 查询时(NH LINQ?QueryOver?你用什么),一定要限制返回的记录。不要 .ToList() 整个表只显示 20。使用 Skip/Take。
  • 注意SELECT N+1 问题。这会扼杀你在任何 OR/M 上的表现。

这些将是我的建议,代码看不见。

更新:所以主要问题似乎是 10-15 秒的启动,这可能是 Application_Start 期间的 SessionFactory 初始化时间。

我还没有尝试过,但一般建议快速启动时间是将 NH 配置对象序列化到磁盘(其中包含映射),并在每次启动时加载它(原始缓存)。如果映射发生变化,您需要检测到这一点,或者进行手动加载(删除序列化配置文件)。

在您的代码中,您使用 Fluent NHibernate 构建 FluentNHibernate.Cfg.FluentConfiguration 实例,并在其上调用 cfg.BuildSessionFactory() 以返回新的 ISessionFactory。您需要序列化的配置是 NHibernate.Cfg.Configuration。所以你可能会把你的代码修改成这样:

    public static ISessionFactory CreateSessionFactory()
    {
        string conStringName = "ConnectionString";

        // http://weblogs.asp.net/ricardoperes/archive/2010/03/31/speeding-up-nhibernate-startup-time.aspx
        System.Runtime.Serialization.IFormatter serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

        NHibernate.Cfg.Configuration cfg = null;

        if (File.Exists("Configuration.serialized"))
        {
            using (Stream stream = File.OpenRead("Configuration.serialized"))
            {
                cfg = serializer.Deserialize(stream) as Configuration;
            }
        }
        else
        {
            // file not exists, configure normally, and serialize NH configuration to disk
            cfg = Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2008
                .ConnectionString(c => c.FromConnectionStringWithKey(conStringName)))
                .Mappings(m => m.FluentMappings.Add<Entity1>())
                .Mappings(m => m.FluentMappings.Add<Entity2>())
                .Mappings(m => m.FluentMappings.Add<Entity3>())
                .ExposeConfiguration(p => p.SetProperty("current_session_context_class", "web"))
                .BuildConfiguration();

            using (Stream stream = File.OpenWrite("Configuration.serialized"))
            {
                serializer.Serialize(stream, cfg);
            }
        }

        return cfg.BuildSessionFactory();
    }

这会将配置缓存到磁盘,因此您的应用程序启动速度会很快。当然,当您更改 NH 映射时,您必须检测到并重新加载 Fluent 配置,否则手动删除缓存文件。

其他一些调整评论:

  • 你有 .Mappings(m => m.FluentMappings.Add()).Mappings(m => m.FluentMappings.Add()) 等等。这里只是猜测,但一个接一个添加可能会创建多个 HBM 文件在引擎盖下。您可以尝试从外部程序集添加映射,并使用 .Mappings(M => M.FluentMappings.AddFromAssemblyOf())
  • 实际上你不应该在 Application_Start() 中执行 SessionFactory.OpenSession()。只需在那里创建 SessionFactory,然后在您的代码中访问 SessionFactory.GetCurrentSession()。你的 global.asax 应该有:

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        // we open one NH session for every web request, 
        var nhsession = SessionFactory.OpenSession();
        // and bind it to the SessionFactory current session
        CurrentSessionContext.Bind(nhsession);
    }
    
    protected void Application_EndRequest(object sender, EventArgs e)
    {
        // close/unbind at EndRequest
        if (SessionFactory != null)
        {
            var nhsession = CurrentSessionContext.Unbind(SessionFactory);
            nhsession.Dispose();
        }
    }
    

这将是按请求进行会话的方式。

于 2012-05-25T22:13:30.140 回答
3

几个额外的点: -

  1. 您是否在 bin 文件夹中写入任何内容?这会导致应用程序回收,然后导致会话工厂被重建。
  2. 当您运行 NProf 在 Management Studio 中向您显示的 SQL 时,是否需要时间。你完全索引了吗?
  3. 三重检查您没有通过引入登录多次构建会话工厂Application_start

这些问题要么与 SQL server 有关,要么与代码有关,NHibernate 不是罪魁祸首!

另外我认为您需要在代码中添加跟踪以查看真正的瓶颈在哪里或使用ANTS 分析器(您可以获得 14 天的免费试用期)

于 2012-05-26T05:52:27.037 回答
2

@raulg 提出了一些重要的观点。还要考虑:

  1. 将您的 NH 配置序列化到磁盘并在应用程序启动时加载它。谷歌搜索一些例子。
  2. 检查您的 NHibernate 日志记录!这会把人赶出去。如果您使用 log4net 或类似的,请确保您没有记录“所有”您的 NHibernate 信息,因为这会杀死性能并且应该只用于调试问题。
  3. 小心在映射中使用 not.lazyload - 您最终可能会在没有意识到的情况下在查询中加载整个数据库!根据需要急切加载查询。

坚持下去——它是一个很棒的工具。

序列化 NH:

  if (_loadConfiguration && _configurationFile.Exists && _IsConfigurationFileValid())
            {
                configuration = _LoadConfigurationFromFile();
            }
            else
            {
                configuration = Fluently.Configure()
                    .Database(MsSqlConfiguration.MsSql2008
                        .ConnectionString(x => x.FromConnectionStringWithKey("Development"))
                        #if DEBUG
                        .ShowSql()
                        #endif
                    )
                    .ProxyFactoryFactory("NHibernate.Bytecode.DefaultProxyFactoryFactory, NHibernate")
                    .Mappings(mappings => mappings.FluentMappings.AddFromAssemblyOf<UserMap>()
                                              .Conventions.Setup(MappingConventions.GetConventions()))
                    .Mappings(mappings => mappings.HbmMappings.AddFromAssemblyOf<UserMap>())
                    .BuildConfiguration();

                _SaveConfigurationToFile(configuration);
            }

  private bool _IsConfigurationFileValid()
        {
            var assInfo = new FileInfo(Assembly.GetCallingAssembly().Location);
            return _configurationFile.LastWriteTime >= assInfo.LastWriteTime;
        }

        private void _SaveConfigurationToFile(Configuration configuration)
        {
            //#if DEBUG
            //return;
            //#endif
            _logger.Debug("Starting to save NHibernate configuration to " + _configurationFile.FullName);
            using(var file = _configurationFile.OpenWrite())
            {
                new BinaryFormatter().Serialize(file, configuration);
                file.Close();
            }
            _logger.Debug("Finished saving NHibernate configuration to " + _configurationFile.FullName);
        }

        private Configuration _LoadConfigurationFromFile()
        {
            //#if DEBUG
            //    return null;
            //#endif
            _logger.Debug("Starting to load NHibernate configuration from " + _configurationFile.FullName);
            using(var file = _configurationFile.OpenRead())
            {
                var binaryFormatter = new BinaryFormatter();
                var config = binaryFormatter.Deserialize(file) as Configuration;
                file.Close();
                _logger.Debug("Finished loading NHibernate configuration from " + _configurationFile.FullName);
                return config;
            }
        }
于 2012-05-26T05:16:04.870 回答