18

I have implement testing app. which uses fluent nhibernate mapping to db object inside mssql db. Since I want to learn fine tune nhib. mvc3 applications, I'm using this app. for testing purposes which have only one simple entity with 10 enum properties and one string property. So, it is really lightwave, yet startup time according to nhibernate profiler is 4.37 sec. Which is really slow for rendering one entity with few lines checked/unchecked property.

Code is the following. Domain.SessionProvider.cs

public static ISessionFactory CreateSessionFactory()
{
   var config = Fluently.Configure()
          .Database(MsSqlConfiguration.MsSql2008
          .ConnectionString(c => c.FromConnectionStringWithKey("myConnection")))
          .Mappings(m => m.FluentMappings.Add<FeaturesMap>())
          .ExposeConfiguration(p => p.SetProperty("current_session_context_class", "web"))
          .BuildConfiguration();

          return config.BuildSessionFactory();            
}

Global.asax

public class MvcApplication : System.Web.HttpApplication
{   
   //SessionPerWebRequest is ommited here as well as other content
   public static ISessionFactory SessionFactory =
               SessionProvider.CreateSessionFactory();

    protected void Application_Start()
    {
       SessionFactory.OpenSession();
    }
}

Inside myController I have following:

public ActionResult Index()
{
   return View(GetData());
}

private IList<FeaturesViewModel> GetData()
{
     List<Features> data;
     using (ISession session = MvcApplication.SessionFactory.GetCurrentSession())
     {
          using (ITransaction tx = session.BeginTransaction())
          {
              data = session.Query<Features>().Take(5).ToList();
              tx.Commit();

              var viewModelData = FeaturesViewModel.FromDomainModel(data);
              return viewModelData;
           }
      }
}
BobRock
  • 3,477
  • 3
  • 31
  • 48
  • 1
    Is the startup time really that important? In a proper deployment this cost should not occur often. – Lucero May 26 '12 at 13:36
  • regarding to web apps. I think that this startup time using this lightwave entity is "something". You're saying that I check deployment? – BobRock May 26 '12 at 13:38
  • What I'm saying is that if this happens once every day or every several days in a deployed environment (whenever your application pool gets recycled), that cost is not a dealbreaker. – Lucero May 26 '12 at 13:41
  • yep, but you must consider that this is really lightwave entity, so it will progressively grow with more entities to map inside my session factory. Imagine 50 entities with even more complex logic and relationships. So I do need to profile this. Any sugg. – BobRock May 26 '12 at 13:46
  • I don't think that this cost can be scaled like that; I suspect that the majority of it is general initialization and not in relation with the one entity; adding more entities should not multiply the initialization time. – Lucero May 26 '12 at 13:50
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/11775/discussion-between-bobrock-and-lucero) – BobRock May 26 '12 at 13:56
  • One option is to serialize the configuration object to file. Then you only have to build the session factory on app start. Cuts the time down significantly... – dotjoe May 26 '12 at 14:47
  • Why do you open a session on app start?? – dotjoe May 26 '12 at 14:49
  • @dotjoe what to do you suggest where to move open session. Since this is mvc app it seems logical to me to put here so I can call from controller current session to implement session per web request. Please correct me if I going wrong. – BobRock May 26 '12 at 15:16
  • session-per-request means that you open a **new** session for each request. It's very cheap to open a session. – dotjoe May 26 '12 at 15:26
  • so whats are your concrete example bedides serialization of conf.object which I'll give a shot asap. – BobRock May 26 '12 at 15:29
  • 1
    I do something very similar to this example http://ayende.com/blog/4809/refactoring-toward-frictionless-odorless-code-what-about-transactions ...basically an actionfilter that handles the transaction and session disposal and also sets the ISession property on my base controller. – dotjoe May 26 '12 at 15:37
  • You just pointed me in the perfect direction. Hero, is what you are my good sir. I wish I could give you 1,000 rep. – Chazt3n Jan 31 '13 at 19:47

2 Answers2

20

You can improve the startup time (of both web and windows applications) by caching the Configurations. The following class will do this job:

using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web;
using NHibernate.Cfg;

namespace NH32AutoMap.Core
{
    public class ConfigurationFileCache
    {
        private readonly string _cacheFile;
        private readonly Assembly _definitionsAssembly;

        public ConfigurationFileCache(Assembly definitionsAssembly)
        {
            _definitionsAssembly = definitionsAssembly;
            _cacheFile = "nh.cfg";
            if (HttpContext.Current != null) //for the web apps
                _cacheFile = HttpContext.Current.Server.MapPath(
                                string.Format("~/App_Data/{0}", _cacheFile)
                                );
        }

        public void DeleteCacheFile()
        {
            if (File.Exists(_cacheFile))
                File.Delete(_cacheFile);
        }

        public bool IsConfigurationFileValid
        {
            get
            {
                if (!File.Exists(_cacheFile))
                    return false;
                var configInfo = new FileInfo(_cacheFile);
                var asmInfo = new FileInfo(_definitionsAssembly.Location);

                if (configInfo.Length < 5 * 1024)
                    return false;

                return configInfo.LastWriteTime >= asmInfo.LastWriteTime;
            }
        }

        public void SaveConfigurationToFile(Configuration configuration)
        {
            using (var file = File.Open(_cacheFile, FileMode.Create))
            {
                var bf = new BinaryFormatter();
                bf.Serialize(file, configuration);
            }
        }

        public Configuration LoadConfigurationFromFile()
        {
            if (!IsConfigurationFileValid)
                return null;

            using (var file = File.Open(_cacheFile, FileMode.Open, FileAccess.Read))
            {
                var bf = new BinaryFormatter();
                return bf.Deserialize(file) as Configuration;
            }
        }
    }
}

To use that,

private Configuration readConfigFromCacheFileOrBuildIt()
{
    Configuration nhConfigurationCache;
    var nhCfgCache = new ConfigurationFileCache(MappingsAssembly);
    var cachedCfg = nhCfgCache.LoadConfigurationFromFile();
    if (cachedCfg == null)
    {
        nhConfigurationCache = buildConfiguration();
        nhCfgCache.SaveConfigurationToFile(nhConfigurationCache);
    }
    else
    {
        nhConfigurationCache = cachedCfg;
    }
    return nhConfigurationCache;
}

And then before calling the BuildSessionFactory, we can read the config file from cache or if the mappings have changed, build it and cache it again:

public ISessionFactory SetUpSessionFactory()
{
    var config = readConfigFromCacheFileOrBuildIt();
    var sessionFactory = config.BuildSessionFactory();

Here you can find a full sample: (^). + If you want to make it work, separate domain classes and mappings definitions assemblies from the main application's assembly (because the ConfigurationFileCache class will delete the cache file if the mappings definitions assembly is newer than the cache file's LastWriteTime).

VahidN
  • 18,457
  • 8
  • 73
  • 117
  • FYI: I've tried this on 3.3 and it makes no difference. Maybe my ssd is faster than older HDs, but the startup problem remains. – Candide Nov 18 '14 at 16:13
2

IIRC correctly, it is not a good idea to create objects in the contructor of HttpApplication (or its subclasses such as MvcApplication). Better to create the session factory in the Application_Start handler.

You should remove NHibernate profiler (since all profilers might affect the measurements). Instead, put the call to CreateSessionFactory() and surround it with use of the Stopwatch class to get an accurate measurement.

Is your database server slow to respond? Because of connection pooling this might only be noticeable on the first occasion.

NHibernate do take some time to initialize, but 4 seconds with one lightweight entity do seem too much. This is of course affected by the general performance of the test system though.

Oskar Berggren
  • 5,583
  • 1
  • 19
  • 36
  • having this code above in mind may I ask you to show me how should look like Application_Start method. Thanks – BobRock May 26 '12 at 15:22
  • In relation to you original code, just move the call to CreateSessionFactory() into Application_Start(), and the call to OpenSession() into Application_BeginRequest(). Add suitable code to close the session in Application_EndRequest(). Using Begin|EndRequest corresponds to the session-per-request pattern. – Oskar Berggren May 29 '12 at 12:44