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.