Here's a little proof of concept I did. I realize it's not production quality. All suggestions to make it so are more than welcome!
There are two classes (a usage example follows at the end):
Code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Transactions;
using NHibernate;
public class HibernateUtil
{
private Dictionary<string, ISession> map = new Dictionary<string, ISession>();
internal static HibernateUtil instance = new HibernateUtil();
private ISessionFactory sessionFactory;
private HibernateUtil()
{
}
public static ISessionFactory SessionFactory
{
set
{
instance.sessionFactory = value;
}
}
public static ISession CurrentSession
{
get
{
return instance.BuildCurrentSession();
}
}
private ISession BuildCurrentSession()
{
Transaction currentTx = Transaction.Current;
if (currentTx == null)
{
throw new ApplicationException("No System.Transactions.Transaction present");
}
string txId = currentTx.TransactionInformation.LocalIdentifier;
ISession session = null;
if(!map.ContainsKey(txId))
{
session = sessionFactory.OpenSession();
map[txId] = session;
currentTx.EnlistVolatile(new SessionEnlistmentNotification(session, txId),
EnlistmentOptions.EnlistDuringPrepareRequired);
Debug.WriteLine("Returning new session for TX "+txId);
}
else
{
session = map[txId];
Debug.WriteLine("Returning existing session for TX " + txId);
}
return session;
}
internal void Remove(string transactionId)
{
Debug.WriteLine("Cleaning up TX " + transactionId);
map.Remove(transactionId);
}
}
and
Code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Transactions;
using NHibernate;
class SessionEnlistmentNotification : IEnlistmentNotification
{
private ISession session;
private string transactionId;
public SessionEnlistmentNotification(ISession session, string transactionId)
{
this.session = session;
this.transactionId = transactionId;
}
void IEnlistmentNotification.Commit(Enlistment enlistment)
{
Debug.WriteLine("In Commit...");
enlistment.Done();
}
void IEnlistmentNotification.InDoubt(Enlistment enlistment)
{
Debug.WriteLine("In InDoubt...");
enlistment.Done();
}
void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
{
Debug.WriteLine("In Prepare...");
try
{
session.Flush();
Close();
}
catch (Exception e)
{
preparingEnlistment.ForceRollback();
throw e;
}
preparingEnlistment.Prepared();
}
void IEnlistmentNotification.Rollback(Enlistment enlistment)
{
Debug.WriteLine("In Rollback...");
session.Clear();
Close();
enlistment.Done();
}
private void Close()
{
session.Close();
HibernateUtil.instance.Remove(transactionId);
this.session = null;
}
}
It can be used like this:
Code:
public class NetworkDemo
{
[STAThread]
public static void Main(string[] args)
{
// configure the configuration
Configuration ds = new Configuration()
.AddClass(typeof(Vertex))
.AddClass(typeof(Edge));
//build a session factory
ISessionFactory sessions = ds.BuildSessionFactory();
// inject the session factory into the HibernateUtil singleton (to be done once)
HibernateUtil.SessionFactory = sessions;
using (TransactionScope txScope = new TransactionScope(TransactionScopeOption.Required))
{
DoSomeBusiness();
DoSomeOtherBusiness();
txScope.Complete();
}
}
public static void DoSomeBusiness()
{
ISession session = HibernateUtil.CurrentSession;
Vertex v = new Vertex();
v.Name = "test-vertex-9";
session.Save(v);
}
public static void DoSomeOtherBusiness()
{
}
}
Please note we don't do an explicit session flush in the business method, nor are there any calls to the Hibernate's ITransaction. This, of course, makes the code only usable within a TransactionScope.
Johan.