NHibernate with Autofac within ASP.NET (MVC): ITransaction - asp.net-mvc

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.

I usually manage the transaction myself..
public ActionResult Edit(Question q){
try {
using (var t = repo.BeginTransaction()){
repo.Save(q);
t.Commit();
return View();
}
catch (Exception e){
...
}
}

Related

What would cause NHibernate's Save method to fail silently?

I've been tasked with taking over an existing ASP.NET MVC 2.0 web application that was developed by a third party developer who is no longer around to provide any assistance. There has been a requirement to add some functionality to the project, which required a project upgrade to .NET 4.5, which has been performed.
The sites underlying MSSQL 2008 R2 database access has been implemented using NHibernate version 2.0.1.4000, along with Castle and FluentNHibernate.
This is the first project I've been involved in that has used NHibernate, and I've hit a problem that has me stumped. The problem did not exist until the upgrade to .NET 4.5.
All database operations are working normally, except for one. Saving a particular object (
Opportunity type) to the database (this object directly maps to an Opportunity database table) fails. Prior to saving (in this case a SQL UPDATE statement), the object has new values set. But the record in the database always has the old values after saving.
Hooking up log4net to view the debug code, shows that the record is indeed updated, but using the old values in the UPDATE statement.
Surprisingly, the Opportunity object is intially saved using the same Save method (albeit via a different action method), and that is saving to the database just fine.
So my question is, what would cause this to happen? Being that I'm not an NHibernate expert, is it the case that the NHibernate version is simply incompatible with .NET 4.5? Or can anyone provide a pointer as to what the problem might be? I'm happy to show any code, but as there is so much I would need to know what. Below is a starter:
The Global.asax has the following references to NHibernate:
private static void MvcApplication_BeginRequest(object sender, System.EventArgs e)
{
NHibernateSessionManager.Instance.BeginTransaction();
}
private static void MvcApplication_EndRequest(object sender, System.EventArgs e)
{
NHibernateSessionManager.Instance.CommitTransaction();
}
The NHibernateSessionManager class is defined as (Opportunity derives from DomainBase):
public sealed class NHibernateSessionManager
{
private ISessionFactory sessionFactory;
private Configuration config;
#region Thread-safe, lazy Singleton
public static NHibernateSessionManager Instance
{
get
{
return Nested.nHibernateSessionManager;
}
}
private NHibernateSessionManager()
{
InitSessionFactory();
}
private class Nested
{
internal static readonly NHibernateSessionManager nHibernateSessionManager = new NHibernateSessionManager();
}
#endregion
private void InitSessionFactory()
{
var autoMappings = AutoPersistenceModel.MapEntitiesFromAssemblyOf<DomainBase>()
.Where(type =>
typeof(DomainBase).IsAssignableFrom(type) &&
type.IsClass &&
!type.IsAbstract)
.WithSetup(s =>
{
s.IsBaseType = type =>
type == typeof (DomainBase);
})
.UseOverridesFromAssemblyOf<OpportunityMappingOverride>()
.ConventionDiscovery.Add(DefaultLazy.AlwaysTrue())
.ConventionDiscovery.Add<CascadeAllHasOneConvention>()
.ConventionDiscovery.Add<CascadeAllHasManyConvention>()
.ConventionDiscovery.Add<CascadeAllReferenceConvention>();
sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005
.ConnectionString(c => c.FromConnectionStringWithKey("Default"))
.UseReflectionOptimizer()
.Cache(c => c.UseQueryCache().UseMininmalPuts().ProviderClass<SysCacheProvider>())
.ShowSql())
.Mappings(m => m.AutoMappings.Add(autoMappings))
.ExposeConfiguration(SetConfiguration)
.BuildSessionFactory();
}
private void SetConfiguration(Configuration cfg)
{
config = cfg;
}
public void RegisterInterceptor(IInterceptor interceptor)
{
ISession session = threadSession;
if (session != null && session.IsOpen)
{
throw new CacheException("You cannot register an interceptor once a Session has already been opened");
}
GetSession(interceptor);
}
public void GenerateSchema()
{
new SchemaExport(config).Execute(false, true, false, false);
}
public ISession GetSession()
{
return GetSession(null);
}
private ISession GetSession(IInterceptor interceptor)
{
ISession session = threadSession;
if (session == null)
{
if (interceptor != null)
{
session = sessionFactory.OpenSession(interceptor);
}
else
{
session = sessionFactory.OpenSession();
}
threadSession = session;
}
return session;
}
public void CloseSession()
{
ISession session = threadSession;
threadSession = null;
if (session != null && session.IsOpen)
{
session.Close();
}
}
public void BeginTransaction()
{
ITransaction transaction = threadTransaction;
if (transaction == null)
{
transaction = GetSession().BeginTransaction();
threadTransaction = transaction;
}
}
public void CommitTransaction()
{
ITransaction transaction = threadTransaction;
try
{
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Commit();
threadTransaction = null;
}
}
catch (HibernateException)
{
RollbackTransaction();
throw;
}
}
public void RollbackTransaction()
{
ITransaction transaction = threadTransaction;
try
{
threadTransaction = null;
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Rollback();
}
}
finally
{
CloseSession();
}
}
private static ITransaction threadTransaction
{
get
{
return (ITransaction)CallContext.GetData("THREAD_TRANSACTION");
}
set
{
CallContext.SetData("THREAD_TRANSACTION", value);
}
}
private static ISession threadSession
{
get
{
return (ISession)CallContext.GetData("THREAD_SESSION");
}
set
{
CallContext.SetData("THREAD_SESSION", value);
}
}
}
I'm hoping I won't get shot down for this question being too general. I've spent a day trying to work out what is happening, including extensive searches online.
It turned out the problem was that the NHibernateSessionManager class was storing its ITransaction and ISession objects in System.Runtime.Remoting.Messaging.CallContext.
Swapping it out to store the objects in the HttpContext.Current.Items collection resolved the issue.
I found this post which implies .NET 4.5 handles CallContext slightly differently compared with previous versions, which obviously caused my issue.
Because the NHibernateSessionManager class was in a class library that was also used by a couple of rarely used console applications, I left a fallback to the CallContext object as per below (not pretty, and there might have been an better alternative, but worked for me [subject to testing], as I've spent far to long figuring this one out using remote debugging):
private static ITransaction threadTransaction
{
get
{
try
{
return (ITransaction)System.Web.HttpContext.Current.Items["THREAD_TRANSACTION"];
}
catch
{
return (ITransaction)CallContext.GetData("THREAD_TRANSACTION");
}
}
set
{
try
{
System.Web.HttpContext.Current.Items["THREAD_TRANSACTION"] = value;
}
catch
{
CallContext.SetData("THREAD_TRANSACTION", value);
}
}
}
private static ISession threadSession
{
get
{
try
{
return (ISession)System.Web.HttpContext.Current.Items["THREAD_SESSION"];
}
catch
{
return (ISession)CallContext.GetData("THREAD_SESSION");
}
}
set
{
try
{
System.Web.HttpContext.Current.Items["THREAD_SESSION"] = value;
}
catch
{
CallContext.SetData("THREAD_SESSION", value);
}
}
}

recommendation pattern to use for handling sessions per web requests in mvc using NHibernate

For example.
My session factory is located in MyDomain.SessionProvider class.
Session can be open using ISession session = SessionProvider.Instance.OpenSession()
Step: SessionProvider.cs
public static SessionProvider Instance { get; private set; }
private static ISessionFactory _SessionFactory;
static SessionProvider()
{
var provider = new SessionProvider();
provider.Initialize();
Instance = provider;
}
private SessionProvider()
{
}
private void Initialize()
{
string csStringName = "ConnectionString";
var cfg = Fluently.Configure()
//ommiting mapping and db conf.
.ExposeConfiguration(c => c.SetProperty("current_session_context_class", "web"))
.BuildConfiguration();
_SessionFactory = cfg.BuildSessionFactory();
}
public ISession OpenSession()
{
return _SessionFactory.OpenSession();
}
public ISession GetCurrentSession()
{
return _SessionFactory.GetCurrentSession();
}
Step: Global.asax.cs
public static ISessionFactory SessionFactory { get; private set; }
Application Start
SessionFactory = SessionProvider.Instance.OpenSession().SessionFactory;
App_BeginRequest
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
EndRequest
dispose session
var session = CurrentSessionContext.Unbind(SessionFactory);
session.Dispose();
Step3.HomeController
I should be using current session like
var session = SessionProvider.Instance.GetCurrentSession();
using (var tran = session.BeginTransaction())
{
//retrieve data from session
}
Now, with trying to retrieve data on my controller like desc. in Step3. I got error message that my session is closed. I tried to remove Application_EndRequest block inside global.asax cause my transaction is wrapped with session but with no success. Still same error.
Second/side question: is this pattern accepted widely, or it is better to wrapped inside custom attributes on mvc controllers. Thanks.
Updated:
On my controller when try to instantiate current session in line
var session = SessionProvider.Instance.GetCurrentSession();
I'm getting following error:
**Connection = 'session.Connection' threw an exception of type 'NHibernate.HibernateException'**
**base {System.ApplicationException} = {"Session is closed"}**
Thanks #LeftyX
I solved this problem using TekPub video Mastering NHibernate with some customizations.
Global.asax
//Whenever the request from page comes in (single request for a page)
//open session and on request end close the session.
public static ISessionFactory SessionFactory =
MyDomain.SessionProvider.CreateSessionFactory();
public MvcApplication()
{
this.BeginRequest += new EventHandler(MvcApplication_BeginRequest);
this.EndRequest +=new EventHandler(MvcApplication_EndRequest);
}
private void MvcApplication_EndRequest(object sender, EventArgs e)
{
CurrentSessionContext.Unbind(SessionFactory).Dispose();
}
private void MvcApplication_BeginRequest(object sender, EventArgs e)
{
CurrentSessionContext.Bind(SessionFactory.OpenSession());
}
protected void Application_Start()
{
SessionFactory.OpenSession();
}
and inside my controller
var session = MvcApplication.SessionFactory.GetCurrentSession();
{
using (ITransaction tx = session.BeginTransaction())
{... omitting retrieving data}
}
You can find a couple of simple and easy implementations here and here and find some code here.
I like Ayende's approach to keep everything simple and clean:
public class Global: System.Web.HttpApplication
{
public static ISessionFactory SessionFactory = CreateSessionFactory();
protected static ISessionFactory CreateSessionFactory()
{
return new Configuration()
.Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "hibernate.cfg.xml"))
.BuildSessionFactory();
}
public static ISession CurrentSession
{
get{ return (ISession)HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
}
protected void Global()
{
BeginRequest += delegate
{
CurrentSession = SessionFactory.OpenSession();
};
EndRequest += delegate
{
if(CurrentSession != null)
CurrentSession.Dispose();
};
}
}
In my projects I've decided to use a IoC container (StructureMap).
In case you're interested you can have a look here.

Autofac, (Fluent) nHibernate, ISession "Session is closed!" intermittently

I'm having random "Session is closed!" errors with the following configuration of Autofac and Fluent nHibernate:
Global.asax.cs:
builder.Register(x => new NHibernateConfigurator().GetSessionFactory()).SingleInstance();
builder.Register(x => x.Resolve<ISessionFactory>().OpenSession()).InstancePerHttpRequest();
NHibernateConfigurator.cs
public class NHibernateConfigurator
{
public Configuration Configure()
{
var configuration = new Configuration();
configuration.SessionFactory()
.Proxy.Through<ProxyFactoryFactory>()
.Integrate.Using<Oracle10gDialect>();
FluentConfiguration fluentConfiguration = Fluently.Configure(configuration);
fluentConfiguration.Mappings(m => m.FluentMappings.AddFromAssemblyOf<UserMap>());
return fluentConfiguration.BuildConfiguration();
}
public ISessionFactory GetSessionFactory()
{
var configuration = Configure();
return configuration.BuildSessionFactory();
}
}
SomeController.cs:
private readonly IRepository repository;
public SomeController(IRepository repository)
{
this.repository = repository
}
[Transaction]
public ActionResult Index()
{
var result = repository.GetUsers();
return View(result);
}
TransactionAttribute.cs
public class TransactionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
DependencyResolver.Current.GetService<ISession>().BeginTransaction(IsolationLevel.ReadCommitted);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
ITransaction currentTransaction = DependencyResolver.Current.GetService<ISession>().Transaction;
if (currentTransaction.IsActive)
{
if (filterContext.Exception != null && filterContext.ExceptionHandled)
{
currentTransaction.Rollback();
}
}
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
ITransaction currentTransaction = DependencyResolver.Current.GetService<ISession>().Transaction;
base.OnResultExecuted(filterContext);
try
{
if (currentTransaction.IsActive)
{
if (filterContext.Exception != null && !filterContext.ExceptionHandled)
{
currentTransaction.Rollback();
}
else
{
currentTransaction.Commit();
}
}
}
finally
{
currentTransaction.Dispose();
}
}
}
IRepository.cs:
public interface IRepository
{
IList<User> GetUsers();
}
Repository.cs:
public class Repository : IRepository
{
private readonly ISession session;
public Repository(ISession session)
{
this.session = session;
}
public IList<User> GetUsers()
{
return session.QueryOver<User>().List();
}
}
This current set-up works, but seems to fail intermittently (after a few page reloads or restarts of Cassini) with GetUsers throwing "Session is closed!" errors. I thought registering the ISessionFactory on an InstancePerHttpRequest would avoid these issues. No luck. Any ideas? New to nHibernate and Autofac, so if I failed to post enough relevant information, let me know.
It appears my configuration is working perfectly for any nHibernate calls outside of my custom membership providers and custom role providers - they are the issue as far as I can tell, which means this question isn't really addressing the issue.
How is your Repository registered? Is it InstancePerHttpRequest (which is should be), or Singleton (which it shouldn't).
I have had a similar problem using MVC 3 action filters and seems they are cached quite aggressively in MVC 3 and found that sessions were not always being opened as OnActionExecuted not always fire.
Move your session into the controller constructor like so:
public SomeController(
ISession session,
ILogger logger,
IRepository<Something> someRepository )
{
_session = session;
_logger = logger;
_someRepository = someRepository;
}
In your action where you want to wrap a transaction:
using (var transaction = _session.BeginTransaction())
{
// do something with your repository
_someRepository.Add(new Something());
transaction.commit();
}

Fluent Nhibernate

I am trying to use Fluent NHibernate with ASP.NET MVC 3 and I cannot seem to find a tutorial that explains how to get it all configured with ASP.NET MVC. I mainly am wondering where to put the ISession building function and how to call it when I need it. I see so many different implementations but none of them specify where they put this code. So if anyone can explain how to get it all configured to work with MVC 3 or where a very detailed tutorial is, that would be greatly appreciated.
You can have a look at S#arp Architecture.
It's a pretty solid architectural framework to work with ASP.NET MVC & NHibernate. They have a decent documentation and there's some sample projects to look at.
http://www.sharparchitecture.net/
If you are not using dependency injection you can try something like this
public class MvcApplication : System.Web.HttpApplication
{
public static ISession CurrentSession
{
get { return (ISession)HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
}
private static ISessionFactory _session_factory;
private static object _session_factory_lock = new object();
protected static ISessionFactory CreateSessionFactory()
{
if (_session_factory != null) return _session_factory;
if (ConfigurationManager.ConnectionStrings["DbConnection"] != null)
{
var conn = ConfigurationManager.ConnectionStrings["DbConnection"];
SqlServerSessionFactoryBuilder fb = new SqlServerSessionFactoryBuilder(conn.ConnectionString);
_session_factory = fb.GetSessionFactory();
return _session_factory;
}
throw new Exception("Cannot build session factory, connection string is not defined.");
}
public MvcApplication()
{
_session_factory = CreateSessionFactory();
BeginRequest += delegate
{
try
{
CurrentSession = _session_factory.OpenSession();
}
catch (FluentConfigurationException ex)
{
logger.FatalException(string.Format("Error configuring the database {0}", ex.Message), ex);
}
};
EndRequest += delegate
{
if (CurrentSession != null)
{
if (CurrentSession.Transaction != null && CurrentSession.Transaction.IsActive)
{
logger.Error("Rolling back uncommited transaction");
CurrentSession.Transaction.Rollback();
}
else
{
CurrentSession.Flush();
}
CurrentSession.Close();
}
};
Error += delegate
{
var error = this.Server.GetLastError();
logger.ErrorException(string.Format("Unhandled error : {0}", error.Message), error);
};
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
}

SQL CE 4 System.Transaction support

A similar question was asked here but had no answer.
I am attempting to use a System.Transactions.CommittableTransaction with EF CTP4 and SQL CE 4.
I have created the following transaction attribute for my ASP.NET MVC Controller actions:
public class TransactionAttribute : ActionFilterAttribute
{
CommittableTransaction transaction;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
transaction = new CommittableTransaction();
Transaction.Current = transaction;
base.OnActionExecuting(filterContext);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
try
{
var isValid = filterContext.Exception == null || filterContext.ExceptionHandled;
if (filterContext.Controller.ViewData.ModelState.IsValid && isValid) {
transaction.Commit();
} else {
transaction.Rollback();
Transaction.Current = null;
}
}
finally
{
transaction.Dispose();
}
}
}
When I use this filter I get the error:
System.InvalidOperationException: The connection object can not be enlisted in transaction scope.
However, the following test passes:
[Test]
public void Transaction_rolls_back_if_exception()
{
var transaction = new CommittableTransaction();
Transaction.Current = transaction;
try
{
var project = new Project { Title = "Test" };
projectRepo.SaveOrUpdate(project);
context.SaveChanges();
var post = new Post { Title = "Some post" };
blogRepo.SaveOrUpdate(post);
throw new Exception();
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Transaction.Current = null;
}
projectRepo.GetAll().Count().ShouldEqual(0);
blogRepo.GetAll().Count().ShouldEqual(0);
}
Has this something to do with how I am initializing the DbContext?
I ran into this same issue. You cannot use Transactions with the CE Connection. I ended up having my datacontext manage my ce connection and implementing a unit of work pattern to hold my actions, then executing all the scheduled actions inside a SqlCeTransaction then calling commit or rollback myself.
http://msdn.microsoft.com/en-us/library/csz1c3h7.aspx

Resources