Josh Anderson
Josh Anderson

Reputation: 6005

NHibernate / MVC lazy loading failing with "session closed or no session"

I'm using NHibernate behind my ASP.NET MVC application and I've come across a frustrating problem when trying to save an object via an AJAX call. I am getting the usual:

Failed to lazily initialize a collection of role: [type] no session or session was closed

The problem is, as far as I can tell the session is not closed. I'm using an HttpModule to handle my sessions per the session per request pattern with NHibernate set to use the web current_session_context_class. Here's the HttpModule:

public class NHHttpModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.EndRequest += ApplicationEndRequest;
        context.BeginRequest += ApplicationBeginRequest;
    }

    public void ApplicationBeginRequest(object sender, EventArgs e)
    {
        CurrentSessionContext.Bind(SessionFactory.GetNewSession());
    }

    public void ApplicationEndRequest(object sender, EventArgs e)
    {
        var currentSession = CurrentSessionContext.Unbind(SessionFactory.GetSessionFactory());
        currentSession.Close();
        currentSession.Dispose();
    }

    public void Dispose()
    {
        // Do nothing
    }
}

My SessionFactory is pretty standard, too:

public static class SessionFactory
{
    private static ISessionFactory _sessionFactory;

    private static void Init()
    {
        _sessionFactory = Fluently.Configure() //Lots of other stuff here for NH config
            .BuildSessionFactory();
    }

    public static ISessionFactory GetSessionFactory()
    {
        if (_sessionFactory == null)
            Init();

        return _sessionFactory;
    }

    public static ISession GetNewSession()
    {
        return GetSessionFactory().OpenSession();
    }

    public static ISession GetCurrentSession()
    {
        return GetSessionFactory().GetCurrentSession();
    }
}

I'm using a unit of work for transactions, which is why I don't open a transaction at the BeginRequest. Even so, I've tried that with no change in results.

I'm trying to save a Comment object to a User object via an AJAX post. Here is the controller code:

    [HttpPost, ValidateInput(false)]
    public ActionResult CreateCommentAsync(CommentCreateViewModel model)
    {
        if (!model.UserId.HasValue)
            return Content("false");

        var svc = DependencyResolver.Current.GetService<IPartnerUserService>();
        var user = svc.FindBy(model.UserId.Value, UserContext.Current.ActiveUser);

        // I put this in here as a test -- it throws the no-session error, too.            
        var count = user.Comments.Count();

        var comment = new Comment();
        comment.CommentText = model.CommentText;
        comment.DateCreated = DateTime.UtcNow;
        comment.CreatedBy = UserContext.Current.ActiveUser;

        // This is the original source of the error
        user.Comments.Add(comment);
        svc.Save(user, UserContext.Current.ActiveUser);

        return Content("true");
    }

I have debugged the application and confirmed that a session is created at the beginning of the request, and, most confusing, the SessionFactory.GetCurrentSession().IsOpen is true, even when I hit a breakpoint for the errors listed above.

Furthermore, the Comments list is populated when I render a view that displays a list of comments. I can't figure out why it's failing when I add it.

As if that weren't enough, every once in a while, with no changes to the code, I don't get the error and can successfully add a comment. I'm pulling my hair out...any ideas? This is certainly a session management issue, but I've gone over everything I can find online and by all accounts the way I'm doing session management is ok. Any ideas?

UPDATE: I've tried a few additional tests, most notably whether the current session has the user object I'm trying to manipulate. It does. When I test like this:

if (!SessionFactory.GetCurrentSession().Contains(user))
    SessionFactory.GetCurrentSession().Refresh(user);

I get a result of true on the condition.

A commenter requested the code on the service, but for that particular call it doesn't touch a session, it just verifies that the requesting user has permissions then sets up the detached criteria. The repository is then called within that service, and here's that code:

public IEnumerable<T> FindBy(DetachedCriteria detachedCriteria) //Infrastructure.Querying.Query query)
{
    return detachedCriteria.GetExecutableCriteria(SessionFactory.GetCurrentSession()).Future<T>();
}

The reason I don't think this code is the problem is that it's exactly the same code called for the details view. I don't have any lazy loading errors when I display the comments, and I do it the same way - I use the service to load the user object then do a foreach iteration through the list. I've NEVER had a problem doing that.

In case this was some sort of issue with the AJAX call, I also changed it to a full postback, but still got the same error.

I can't for the life of me figure out what's going on here.

Upvotes: 3

Views: 2403

Answers (3)

Josh Anderson
Josh Anderson

Reputation: 6005

I finally discovered the reason for this error, but only by dumb luck. I'll post the resolution in case it helps someone, but I can't really offer any explanation why and will probably post a new question to see if someone can clear the air.

You'll note in my code I was calling my service like this:

var user = svc.FindBy(model.UserId.Value, UserContext.Current.ActiveUser);

That UserContext object is a static class with a session-stored Current instance that contains a property which is the current user record NHibernate object. Because of how NHibernate proxies properties, that UserContext.ActiveUser property had to do something like this whenever it was called:

if (!SessionFactory.GetCurrentSession().Contains(ActiveUser))
    SessionFactory.GetCurrentSession().Refresh(ActiveUser);

For some reason, it was this refresh process that was screwing things up. When I explicitly retrieved the active user instead of using the UserContext class, everything worked fine.

I've since changed how I retrieve the active user so that it's not using a session-stored instance and it's working fine. I wish I knew exactly why, but I don't!

Upvotes: 1

Mariusz
Mariusz

Reputation: 3154

This exception is thrown also when you want to do something with session in the view.
Check this thread

Upvotes: 0

danyolgiax
danyolgiax

Reputation: 13086

It is possible that there is a problem with the collection mapping. Are you sure mapping is correct and entities are properly binded?

Check one-to-many and many-to-one and inverse attribute.

updated

have you tried to attach "user" entity with session.Lock(..)?

I can suggest you to try to save single user entity or single comment entity to test if the problem comes from comment collection.

Upvotes: 0

Related Questions