Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Don't flush the session after an exception occurs - NHibernate

I am developing a ASP.NET MVC web app under .NET 3.5, NHibernate and hosted on Windows Azure. When, the webapp is run from the local development fabric it works fine. Yet, when I move it to Windows Azure, every insert performed from the MVC web role ends up with the exception listed below.

Any idea what's wrong with my NHibernate logic? (might be the session management, not sure)

[AssertionFailure: null id in Lokad.Translate.Entities.User entry (don't flush the Session after an exception occurs)] NHibernate.Event.Default.DefaultFlushEntityEventListener.CheckId(Object obj, IEntityPersister persister, Object id, EntityMode entityMode) +292 NHibernate.Event.Default.DefaultFlushEntityEventListener.GetValues(Object entity, EntityEntry entry, EntityMode entityMode, Boolean mightBeDirty, ISessionImplementor session) +93 NHibernate.Event.Default.DefaultFlushEntityEventListener.OnFlushEntity(FlushEntityEvent event) +158 NHibernate.Event.Default.AbstractFlushingEventListener.FlushEntities(FlushEvent event) +469 NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent event) +339 NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event) +85 NHibernate.Impl.SessionImpl.Flush() +275 NHibernate.Transaction.AdoTransaction.Commit() +236 Lokad.Translate.Repositories.PageRepository.Create(Page page) Lokad.Translate.Controllers.PagesController.Create(Page page) lambda_method(ExecutionScope , ControllerBase , Object[] ) +69 System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary2 parameters) +251 System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary2 parameters) +31 System.Web.Mvc.<>c__DisplayClassa.b__7() +88 System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func1 continuation) +534 System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +312 System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +856 System.Web.Mvc.Controller.ExecuteCore() +185 System.Web.Mvc.MvcHandler.ProcessRequest(HttpContextBase httpContext) +221 System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +586 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +177

Note that I am using _session.FlushMode = FlushMode.Commit; and that the User is used in a custom RoleProvider

public class SimpleRoleProvider : RoleProvider 
{
    readonly UserRepository Users = new UserRepository();

    public override string[] GetRolesForUser(string username)
    {
        try
        {
            var user = Users.Get(username);

            // no role if user is not registered
            if (null == user) return new string[0];

            // default role for registered user
            return user.IsManager ? new[] { "Manager", "User" } : new[] { "User" };
        }
        catch (Exception)
        {
            // role should not fail in case of DB issue.
            return new string[0];
        }
    }
}
like image 214
Joannes Vermorel Avatar asked Nov 30 '09 10:11

Joannes Vermorel


2 Answers

You should never catch exceptions and ignore them during a NHibernate transaction.

I try to explain why.

There could be exceptions for instance caused by constraints in the database. (it could also be caused by mapping problems, exceptions thrown by properties or anything else.) NHibernate tries to synchronize the state in memory with the database. This is done on commit - and sometimes before queries to make sure that queries are done on actual data. When this synchronization fails, the state in the database is something random, some changes are persisted, others are not. The only thing you can do in such a case is closing the session.

Consider that decisions and calculations in your code are based on values in memory. But - in case of an ignored exception, this values are not the values in the database, they will never be there. So your logic will decide and calculate on 'fantasy-data'.

By the way, it is never a good idea to catch any exception (untyped) and ignore them. You should always know the exceptions you handle, and be sure that you can continue.

What you're doing here is swallowing programming errors. Believe me, the system will not be more stable. The question is only: do you notice the error when it occurs, or do you ignore it there and even persist the result of the error to the database? When you do the latter, you don't have to be surprised when your database is inconsistent and other error arise when you try to get the data from the database. And you will never ever find the code that is the actual cause of the error.

like image 125
Stefan Steinegger Avatar answered Oct 07 '22 06:10

Stefan Steinegger


I have finally found a solution to my own problem. In case people would be interested, I am posting the solution here.

public class SimpleRoleProvider : RoleProvider 
{
    // isolated session management for the RoleProvider to avoid
    // issues with automated management of session lifecycle.

    public override string[] GetRolesForUser(string username)
    {
        using (var session = GlobalSetup.SessionFactory.OpenSession())
        {
            var users = new UserRepository(session);
            var user = users.Get(username);

            // no role if user is not registered
            if (null == user) return new string[0];

            // default role for registered user
            return user.IsManager ? new[] {"Manager", "User"} : new[] {"User"};
        }
    }
}

Basically what was happening is that the RoleProvider repository does not seem to have the same lifecycle than regular in-view / in-controller repositories. As a result, at the time the RoleProvider is called, the NHibernate session has already been disposed causing the exception observed here above.

I have replaced the code by the following one here above. This one has its own NHibernate session management, and ends up working fine.

like image 21
Joannes Vermorel Avatar answered Oct 07 '22 05:10

Joannes Vermorel