title: NHibernate Transaction.Commit()
body: <p>I have a big problem and it concernes transactions in NHibernate.</p>
  
  <p>To kick off I can say that a SessionManager (thread-safe, lazy Singleton) creates a SessionFactory and gives me session objects. Transaction Management is build in.</p>
  
  <p>Next, I have a DAL with hibernate mappings (xml, no fluent) and poco's. </p>
  
  <p>A WCF service is giving some poco's free and that happens via this way:</p>
  
  <pre><code>    public string UpdateCustomer(Customer customer, ExtraParameters extraParameters)
      {
          try
          {
              using (_daof.BeginTransaction(GetInstanceUserID(extraParameters)))
              {
                  return _daof.GetKlantDao().UpdateCustomer(customer).SekKlant.ToString();
              }
          }
          catch (Exception ex)
          {
              ServiceExceptionHandling(ex);
          }
          return null;
      }
  </code></pre>
  
  <p>As you can see, the using statement handles the transaction and the disposing of the session:</p>
  
  <pre><code>  public CrmDaoFactory BeginTransaction(string userID)
      {
          CrmSettings.Instance.UserID = userID; //Not important
          SessionManager.Instance.BeginTransactionOn(configKey); //Memory leak
          return this;
      }
  </code></pre>
  
  <p>Disposing is done via the Disposing Pattern (destructor):</p>
  
  <pre><code>     protected virtual void Dispose(bool disposing)
      {
          if (disposing == true)
          {
              if (Marshal.GetExceptionCode() == 0)
              {
                  SessionManager.Instance.CommitTransactionOn(configKey);
              }
              else
              {
                  SessionManager.Instance.RollbackTransactionOn(configKey);
              }
  
              SessionManager.Instance.CloseSessionOn(configKey);
          }
      }
  
      public void Dispose()
      {
          Dispose(true);
          GC.SuppressFinalize(this);
      }
  
         ~CrmDaoFactory() { Dispose(false); }
  </code></pre>
  
  <p>Above, you can see that when dispose is called, the transaction is commited (flushes also the session) and when an error occured it's rollbacked. Finally, I close the session. </p>
  
  <p>The problem is when I call methods on the service, I notice a memory leak. When I quote the BeginTransaction line, no memory leak occurs. </p>
  
  <pre><code>    public CrmDaoFactory BeginTransaction(string userID)
      {
          CrmSettings.Instance.UserID = userID;
          //SessionManager.Instance.BeginTransactionOn(configKey);
          return this;
      }
  </code></pre>
  
  <p>I'm quite sure all the sessions are closed after the disposal, as I debugged the code. 
  For your info, I use the following 'SessionManager' code:</p>
  
  <pre><code> using System;
  using System.Collections;
  using System.IO;
  using System.Runtime.Remoting.Messaging;
  using System.Web;
  using System.Web.Caching;
  
  using NHibernate;
  using NHibernate.Cache;
  using System.Reflection;
  
  namespace Cevi.Hibernate
  {
      /// &lt;summary&gt;
      /// Handles creation and management of sessions and transactions.  It is a singleton because 
      /// building the initial session factory is very expensive. Inspiration for this class came 
      /// from Chapter 8 of Hibernate in Action by Bauer and King.  Although it is a sealed singleton
      /// you can use TypeMock (http://www.typemock.com) for more flexible testing.
      /// &lt;/summary&gt;
      public sealed class SessionManager
      {
          //private static readonly DataInterceptor _dataInterceptor = new DataInterceptor();
  
          #region Thread-safe, lazy Singleton
  
          /// &lt;summary&gt;
          /// This is a thread-safe, lazy singleton.  See http://www.yoda.arachsys.com/csharp/singleton.html
          /// for more details about its implementation.
          /// &lt;/summary&gt;
      public static SessionManager Instance
      {
          get
          {
              return Nested.SessionManager;
          }
      }
  
      /// &lt;summary&gt;
      /// Private constructor to enforce singleton
      /// &lt;/summary&gt;
      private SessionManager() {  }
  
      /// &lt;summary&gt;
      /// Assists with ensuring thread-safe, lazy singleton
      /// &lt;/summary&gt;
      private class Nested
      {
          static Nested() {  }
          internal static readonly SessionManager SessionManager = new SessionManager();
      }
  
      #endregion
  
      /*
      /// &lt;summary&gt;
      /// This method attempts to find a session factory in the &lt;see cref="HttpRuntime.Cache" /&gt; 
      /// via its config file path; if it can't be found it creates a new session factory and adds
      /// it the cache.  Note that even though this uses HttpRuntime.Cache, it should still work in 
      /// Windows applications; see http://www.codeproject.com/csharp/cacheinwinformapps.asp for an
      /// examination of this.
      /// &lt;/summary&gt;
      /// &lt;param name="sessionFactoryConfigPath"&gt;Path location of the factory config&lt;/param&gt;
      private ISessionFactory GetSessionFactoryFor(string sessionFactoryConfigPath)
      {
          if (string.IsNullOrEmpty(sessionFactoryConfigPath))
              throw new ArgumentNullException("sessionFactoryConfigPath may not be null nor empty");
  
          //  Attempt to retrieve a cached SessionFactory from the HttpRuntime's cache.
          ISessionFactory sessionFactory = (ISessionFactory)HttpRuntime.Cache.Get(sessionFactoryConfigPath);
  
          //  Failed to find a cached SessionFactory so make a new one.
          if (sessionFactory == null)
          {
              if (!File.Exists(sessionFactoryConfigPath))
                  // It would be more appropriate to throw a more specific exception than ApplicationException
                  throw new ApplicationException(
                      "The config file at '" + sessionFactoryConfigPath + "' could not be found");
  
              NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();
              cfg.Configure(sessionFactoryConfigPath);
  
              //  Now that we have our Configuration object, create a new SessionFactory
              sessionFactory = cfg.BuildSessionFactory();
  
              if (sessionFactory == null)
              {
                  throw new InvalidOperationException("cfg.BuildSessionFactory() returned null.");
          }
  
              HttpRuntime.Cache.Add(sessionFactoryConfigPath, sessionFactory, null, DateTime.Now.AddDays(7),
                  TimeSpan.Zero, CacheItemPriority.High, null);
          }
  
          return sessionFactory;
      }
      */
  
      /// &lt;summary&gt;
      /// This method attempts to find a session factory in the &lt;see cref="HttpRuntime.Cache" /&gt; 
      /// via its config file path; if it can't be found it creates a new session factory and adds
      /// it the cache.  Note that even though this uses HttpRuntime.Cache, it should still work in 
      /// Windows applications; see http://www.codeproject.com/csharp/cacheinwinformapps.asp for an
      /// examination of this.
      /// &lt;/summary&gt;
      /// &lt;param name="sessionFactoryConfigPath"&gt;Path location of the factory config&lt;/param&gt;
      public ISessionFactory GetSessionFactoryFor(string sessionFactoryConfigPath)
      {
          if (string.IsNullOrEmpty(sessionFactoryConfigPath))
              throw new ArgumentNullException("sessionFactoryConfigPath may not be null nor empty");
  
          // Attempt to retrieve a cached SessionFactory from the HttpRuntime's cache.
          ISessionFactory sessionFactory = (ISessionFactory)HttpRuntime.Cache.Get(sessionFactoryConfigPath);
  
          // Failed to find a cached SessionFactory so make a new one.
          if (sessionFactory == null)
          {
              string path = sessionFactoryConfigPath;
              if (!Path.IsPathRooted(path))
              // indien het geen absoluut pad is afhankelijk van de context waarin de hibernate laag gebruikt wordt het path anders aanvullen
              {
                  if (HttpContext.Current != null)
                      path = HttpContext.Current.Server.MapPath("~/" + path);
                  else if (!string.IsNullOrEmpty(System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath))
                      path = Path.Combine(System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath, path);
                  else
                  {
                      string assemblyPath = (Assembly.GetEntryAssembly()!=null)?  
                          Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) : 
                          AppDomain.CurrentDomain.BaseDirectory;
                      if (!string.IsNullOrEmpty(assemblyPath))
                          path = Path.Combine(assemblyPath, path);
                  }
              }
  
              if (!File.Exists(path))
                  // It would be more appropriate to throw a more specific exception than ApplicationException
                  throw new ApplicationException(
                  "The config file at '" + sessionFactoryConfigPath + "' could not be found");
  
              NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();
              cfg.Configure(path);
              //cfg.SetInterceptor(new DataInterceptor());
  
              // Now that we have our Configuration object, create a new SessionFactory
              sessionFactory = cfg.BuildSessionFactory();
  
              if (sessionFactory == null)
              {
                  throw new InvalidOperationException("cfg.BuildSessionFactory() returned null.");
              }
  
              HttpRuntime.Cache.Add(sessionFactoryConfigPath, sessionFactory, null, DateTime.Now.AddDays(7),
              TimeSpan.Zero, CacheItemPriority.High, null);
          }
          return sessionFactory;
      }
  
      /// &lt;summary&gt;
      /// 
      /// &lt;/summary&gt;
      /// &lt;param name="sessionFactoryConfigPath"&gt;&lt;/param&gt;
      /// &lt;param name="interceptor"&gt;&lt;/param&gt;
      public void RegisterInterceptorOn(string sessionFactoryConfigPath, IInterceptor interceptor)
      {
          ISession session = (ISession)ContextSessions[sessionFactoryConfigPath];
  
          if (session != null &amp;&amp; session.IsOpen)
          {
              throw new CacheException("You cannot register an interceptor once a session has already been opened");
          }
  
          GetSessionFrom(sessionFactoryConfigPath, interceptor);
      }
  
      /// &lt;summary&gt;
      /// &lt;/summary&gt;
      /// &lt;param name="sessionFactoryConfigPath"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public ISession GetSessionFrom(string sessionFactoryConfigPath)
      {
          return GetSessionFrom(sessionFactoryConfigPath, null);
      }
  
      private ISession GetSessionFrom(string sessionFactoryConfigPath, IInterceptor interceptor)
      {
          ISession session = (ISession)ContextSessions[sessionFactoryConfigPath];
  
          if (session == null)
          {
              session = (interceptor != null)? 
                  GetSessionFactoryFor(sessionFactoryConfigPath).OpenSession(interceptor) : 
                  GetSessionFactoryFor(sessionFactoryConfigPath).OpenSession();
  
              ContextSessions[sessionFactoryConfigPath] = session;
          }
  
          if (session == null)
              // It would be more appropriate to throw a more specific exception than ApplicationException
              throw new ApplicationException("session was null");
  
          return session;
      }
  
      ///&lt;summary&gt;
      ///&lt;/summary&gt;
      ///&lt;param name="sessionFactoryConfigPath"&gt;&lt;/param&gt;
      public void CloseSessionOn(string sessionFactoryConfigPath)
      {
          ISession session = (ISession)ContextSessions[sessionFactoryConfigPath];
          if (session != null &amp;&amp; session.IsOpen)
          {
              session.Close();
              session.Dispose();
          }
          ContextSessions.Remove(sessionFactoryConfigPath);
  
  
      }
  
      ///&lt;summary&gt;
      ///&lt;/summary&gt;
      ///&lt;param name="sessionFactoryConfigPath"&gt;&lt;/param&gt;
      public void BeginTransactionOn(string sessionFactoryConfigPath)
      {
          ITransaction transaction = (ITransaction)ContextTransactions[sessionFactoryConfigPath];
  
          if (transaction == null)
          {
              transaction = GetSessionFrom(sessionFactoryConfigPath).BeginTransaction();
              ContextTransactions.Add(sessionFactoryConfigPath, transaction);
          }
      }
  
      ///&lt;summary&gt;
      ///&lt;/summary&gt;
      ///&lt;param name="sessionFactoryConfigPath"&gt;&lt;/param&gt;
      public void CommitTransactionOn(string sessionFactoryConfigPath)
      {
          ITransaction transaction = (ITransaction)ContextTransactions[sessionFactoryConfigPath];
  
          try
          {
              if (transaction != null &amp;&amp; !transaction.WasCommitted &amp;&amp; !transaction.WasRolledBack)
              {
                  transaction.Commit();
                  ContextTransactions.Remove(sessionFactoryConfigPath);
                  transaction.Dispose();
              }
          }
          catch (HibernateException)
          {
              RollbackTransactionOn(sessionFactoryConfigPath);
              throw;
          }
      }
  
      ///&lt;summary&gt;
      ///&lt;/summary&gt;
      ///&lt;param name="sessionFactoryConfigPath"&gt;&lt;/param&gt;
      public void RollbackTransactionOn(string sessionFactoryConfigPath)
      {
          ITransaction transaction = (ITransaction)ContextTransactions[sessionFactoryConfigPath];
  
          try
          {
              ContextTransactions.Remove(sessionFactoryConfigPath);
  
              if (transaction != null &amp;&amp; !transaction.WasCommitted &amp;&amp; !transaction.WasRolledBack)
              {
                  transaction.Rollback();
              }
          }
          finally
          {
              CloseSessionOn(sessionFactoryConfigPath);
          }
      }
  
      /// &lt;summary&gt;
      /// Since multiple databases may be in use, there may be one transaction per database 
      /// persisted at any one time.  The easiest way to store them is via a hashtable
      /// with the key being tied to session factory.
      /// &lt;/summary&gt;
      private static Hashtable ContextTransactions
      {
          get
          {
              if (CallContext.GetData("CONTEXT_TRANSACTIONS") == null)
              {
                  CallContext.SetData("CONTEXT_TRANSACTIONS", new Hashtable());
          }
  
              return (Hashtable)CallContext.GetData("CONTEXT_TRANSACTIONS");
          }
      }
  
      /// &lt;summary&gt;
      /// Since multiple databases may be in use, there may be one session per database 
      /// persisted at any one time.  The easiest way to store them is via a hashtable
      /// with the key being tied to session factory.
      /// &lt;/summary&gt;
      private static Hashtable ContextSessions
      {
          get
          {
              if (CallContext.GetData("CONTEXT_SESSIONS") == null)
              {
                  CallContext.SetData("CONTEXT_SESSIONS", new Hashtable());
          }
              return (Hashtable)CallContext.GetData("CONTEXT_SESSIONS");
          }
      }
  
      /// &lt;summary&gt;
      /// Opens a stateless session 
      /// &lt;/summary&gt;
      /// &lt;param name="sessionFactoryConfigPath"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public IStatelessSession StatelessSession(string sessionFactoryConfigPath)
      {
          return GetSessionFactoryFor(sessionFactoryConfigPath).OpenStatelessSession();
      }
  }
  }
  </code></pre>
  
  <p>Can someone give me some clue what's going on? I'm running out of ideas.
  Oh, second level cache and query cache are disabled:</p>
  
  <pre><code>&lt;property name="cache.use_second_level_cache"&gt;false&lt;/property&gt;
  &lt;property name="cache.use_query_cache"&gt;false&lt;/property&gt;
  </code></pre>
  
  <p>Thanks</p>