SessionManager
This has explicit support for Conversations - Open/Pause/End - or use the simple OpenSession()/Session.
- The SessionFactory is a thread safe object that should exist for the lifetime of your application (because configuration is expensive). So you should configure and create the factory in Application_Start and hold it in some sort of static/ singleton manager.
- This is a lazy loaded singleton. Consider IoC instead and inject it into your repositories.
- Customize the Configuration.AddAssembly if required. It can be done in a generic way using IoC for the assembly list, but typeof(DomainEntity).Assembly is simpler.
- Configuration can be very slow with lots of mapped classes. This manager optimizes by using a serializesd Configuration on disk (if not in DEBUG). Check the path and write-access if this is enabled.
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Context;
namespace Northwind.Repositories
{
/// <summary>
/// A static (singleton) class to manage NHibernate.
/// Manage NHibernate sessions using <see cref="Session"/>
/// </summary>
public sealed class SessionManager
{
private const string SESSIONKEY = "NHIBERNATE.SESSION";
[ThreadStatic]
private static ISession _Session; //this session is not used in web
private readonly Configuration _configuration;
private readonly ISessionFactory _sessionFactory;
#region Constructor
#region Singleton
public static SessionManager Instance
{
get
{
//#if !INSTANCE
return Singleton.Instance;
//#else
// return new SessionManager();
//#endif
}
}
//#if !INSTANCE
private class Singleton
{
static Singleton() { }
internal static readonly SessionManager Instance = new SessionManager();
}
//#endif
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="SessionManager"/> class.
/// </summary>
private SessionManager()
{
_configuration = RestoreConfiguration();
if (_configuration == null)
{
_configuration = new Configuration();
BuildConfiguration();
}
//get the session factory
_sessionFactory = Configuration.BuildSessionFactory();
}
private void BuildConfiguration()
{
Configuration.Configure(); //configure from the app.config
Configuration.AddAssembly(GetType().Assembly);//default- mapping is in this assembly
//other examples:
//Configuration.AddFile("file.hbm.xml"); //add files
//Configuration.AddAssembly(assembly); //add assembly
//Configuration.AddAssembly(assemblyName); //add assembly by name
//foreach (string assemblyName in assemblyNames) //add enumerable of assemblies
// Configuration.AddAssembly(assemblyName);
#if !DEBUG
SaveConfiguration(Configuration);
#endif
}
#endregion
#region Configuration Serialization
//specify a full path if required
private const string _configurationFilePath = "Configuration.save";
private static void SaveConfiguration(Configuration configuration)
{
IFormatter serializer = new BinaryFormatter();
using (Stream stream = File.OpenWrite(_configurationFilePath))
{
try
{
serializer.Serialize(stream, configuration);
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("No write access to " + _configurationFilePath);
}
}
}
/// <summary>
/// Optimization- you could deploy the serialization file.
/// </summary>
private static Configuration RestoreConfiguration()
{
#if DEBUG
return null;
#endif
if (!File.Exists(_configurationFilePath)) return null;
IFormatter serializer = new BinaryFormatter();
using (Stream stream = File.OpenRead(_configurationFilePath))
{
return serializer.Deserialize(stream) as Configuration;
}
}
#endregion
#region NHibernate Setup
/// <summary>
/// Gets the <see cref="NHibernate.Cfg.Configuration"/>.
/// </summary>
internal Configuration Configuration { get { return _configuration; } }
/// <summary>
/// Gets the <see cref="NHibernate.ISessionFactory"/>
/// </summary>
internal ISessionFactory SessionFactory { get { return _sessionFactory; } }
/// <summary>
/// Closes the session factory.
/// </summary>
public void Close()
{
SessionFactory.Close();
}
internal static bool IsWeb { get { return (HttpContext.Current != null); } }
#endregion
#region NHibernate SessionContext (1.2+)
/// <summary>
/// Opens the conversation (if already existing, reuses it). Call this from Application_BeginPreRequestHandlerExecute
/// </summary>
/// <returns>A <see cref="NHibernate.ISession"/></returns>
public ISession OpenConversation()
{
//you must set <property name="current_session_context_class">web</property> (or thread_static etc)
ISession session = Session; //get the current session (or open one). We do this manually, not using SessionFactory.GetCurrentSession()
//for session per conversation (otherwise remove)
session.FlushMode = FlushMode.Never; //Only save on session.Flush() - because we need to commit on unbind in PauseConversation
session.BeginTransaction(); //start a transaction
CurrentSessionContext.Bind(session); //bind it
return session;
}
/// <summary>
/// Ends the conversation. If an exception occurs, rethrows it ensuring session is closed. Call this (or <see cref="PauseConversation"/> if session per conversation) from Application_PostRequestHandlerExecute
/// </summary>
public void EndConversation()
{
ISession session = CurrentSessionContext.Unbind(SessionFactory);
if (session == null) return;
try
{
session.Flush();
session.Transaction.Commit();
}
catch (Exception)
{
session.Transaction.Rollback();
throw;
}
finally
{
session.Close();
}
}
/// <summary>
/// Pauses the conversation. Call this (or <see cref="EndConversation"/>) from Application_EndRequest
/// </summary>
public void PauseConversation()
{
ISession session = CurrentSessionContext.Unbind(SessionFactory);
if (session == null) return;
try
{
session.Transaction.Commit(); //with flushMode=Never, this closes connections but doesn't flush
}
catch (Exception)
{
session.Transaction.Rollback();
throw;
}
//we don't close the session, and it's still in Asp SessionState
}
#endregion
#region NHibernate Sessions
/// <summary>
/// Explicitly open a session. If you have an open session, close it first.
/// </summary>
/// <returns>The <see cref="NHibernate.ISession"/></returns>
public ISession OpenSession()
{
ISession session = SessionFactory.OpenSession();
if (IsWeb)
HttpContext.Current.Items.Add(SESSIONKEY, session);
else
_Session = session;
return session;
}
/// <summary>
/// Gets the current <see cref="NHibernate.ISession"/>. Although this is a singleton, this is specific to the thread/ asp session. If you want to handle multiple sessions, use <see cref="OpenSession"/> directly. If a session it not open, a new open session is created and returned.
/// </summary>
/// <value>The <see cref="NHibernate.ISession"/></value>
public ISession Session
{
get
{
//use threadStatic or asp session.
ISession session = IsWeb ? HttpContext.Current.Items[SESSIONKEY] as ISession : _Session;
//if using CurrentSessionContext, SessionFactory.GetCurrentSession() can be used
//if it's an open session, that's all
if (session != null && session.IsOpen)
return session;
//if not open, open a new session
return OpenSession();
}
}
#endregion
}
}