What is the best approach to managing NHibernate transaction using Autofac within web application?
My approach to session is
builder.Register(c => c.Resolve<ISessionFactory>().OpenSession())
.ContainerScoped();
For ITransaction
, I have found an example on Google Code, but it relies on HttpContext.Current.Error
when deciding whether to rollback.
Is there a better solution? And what scope NHibernate transaction should have?
I posted this a while ago:
http://groups.google.com/group/autofac/browse_thread/thread/f10badba5fe0d546/e64f2e757df94e61?lnk=gst&q=transaction#e64f2e757df94e61
Modified, so that the interceptor has logging capability and [Transaction] attribute can also be used on a class.
[global::System.AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TransactionAttribute : Attribute
{
}
public class ServicesInterceptor : Castle.Core.Interceptor.IInterceptor
{
private readonly ISession db;
private ITransaction transaction = null;
public ServicesInterceptor(ISession db)
{
this.db = db;
}
public void Intercept(IInvocation invocation)
{
ILog log = LogManager.GetLogger(string.Format("{0}.{1}", invocation.Method.DeclaringType.FullName, invocation.Method.Name));
bool isTransactional = IsTransactional(invocation.Method);
bool iAmTheFirst = false;
if (transaction == null && isTransactional)
{
transaction = db.BeginTransaction();
iAmTheFirst = true;
}
try
{
invocation.Proceed();
if (iAmTheFirst)
{
iAmTheFirst = false;
transaction.Commit();
transaction = null;
}
}
catch (Exception ex)
{
if (iAmTheFirst)
{
iAmTheFirst = false;
transaction.Rollback();
db.Clear();
transaction = null;
}
log.Error(ex);
throw ex;
}
}
private bool IsTransactional(MethodInfo mi)
{
var atrClass = mi.DeclaringType.GetCustomAttributes(false);
foreach (var a in atrClass)
if (a is TransactionAttribute)
return true;
var atrMethod = mi.GetCustomAttributes(false);
foreach (var a in atrMethod)
if (a is TransactionAttribute)
return true;
return false;
}
}
When I use autofac I use the same container scoped method but instead of passing the same session to my Repository/DAO objects I pass an UnitOfWork that is container scoped. The Unit of work has this in the constructor.
private readonly ISession _session;
private ITransaction _transaction;
public UnitOfWork(ISession session)
{
_session = session;
_transaction = session.BeginTransaction();
}
And the dispose is:
public void Dispose()
{
try
{
if (_transaction != null &&
!_transaction.WasCommitted &&
!_transaction.WasRolledBack)
_transaction.Commit();
_transaction = null;
}
catch (Exception)
{
Rollback();
throw;
}
}
I am (ab)using the deterministic disposal stuff in autofac in order to manage this, and well I sort of like it.
The other thing is that I am basically only targeting an ASPNet environment and made a conscious decision that a transaction is tied to a web request. So a transaction per web request pattern.
Because of that I can do this error handling code in an IHttpModule:
void context_Error(object sender, System.EventArgs e)
{
_containerProvider.RequestContainer.Resolve<IUnitOfWork>().Rollback();
}
I haven't taken a look at NHibernate.Burrow too closely but I'm sure there is something there that does most of this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With