Reputation: 6005
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
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
Reputation: 3154
This exception is thrown also when you want to do something with session in the view.
Check this thread
Upvotes: 0
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