2

The error

Initializing[Core.Model.Account#2586]-failed to lazily initialize a collection of role: Core.Model.Account.Roles, no session or session was closed

appears when I try to login in my web app using NHibernate. What I don't understand is how the connection can be closed, because when I debug into the Method calling it, I can the isOpen property of the used NHibernate session is opened.

But as soon as the Lazy Loading kicks in, apparently NHibernate thinks the connection was closed. How can this happen?

I start my NHibernate session in the BeginRequest and close them in EndRequest, as is recommended.

Account.cs (in project/namespace/assembly Core)

public class Account
{
    // ...

    public virtual ISet<Role> Roles
    {
        get;
        set;
    }

    // ...

    public virtual bool HasPermission(Permission permission_)
    {
        foreach (Role role in Roles) {
            if (role.Permissions.Contains(Permission.ALL) || role.Permissions.Contains(permission_)) {
                return true;
            }
        }
        return false;
    }
}

Account.hbm.xml (in project/namespace/assembly Core)

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
    <class name="Core.Model.Account,Core" table="accounts">
        <!-- ... -->
        <set name="Roles" table="account_roles" lazy="true">
            <key column="account"/>
            <many-to-many class="Core.Model.Role" column="role" />
        </set>
    </class>
</hibernate-mapping>

Global.asax (in project/namespace/assembly Web)

public AccountRepository Accounts;
private static ISessionFactory _sessionFactory;
private void Application_Start(object sender_, EventArgs e_)
{
    _sessionFactory = new Configuration().Configure().BuildSessionFactory();
}


private void Application_BeginRequest(object sender_, EventArgs e_)
{
    ISession session = _sessionFactory.OpenSession();
    Accounts.SetSession(session);
    HttpContext.Current.Items.Add("HibernateSession", session);
}

private void Application_EndRequest(object sender_, EventArgs e_)
{
    ISession session = HttpContext.Current.Items["HibernateSession"] as ISession;
    if (session != null && session.IsOpen) {
        session.Close();
        HttpContext.Current.Items.Remove("HibernateSession");
    }
}

DecoratedSession.cs (in project/namespace/assembly Web)

private static HttpSessionState _session
{
    get
    {
        return HttpContext.Current.Session;
    }
}

public static T Get<T>(string key_)
{
    if (_session == null || _session[key_] == null) {
        return default(T);
    }
    return (T)_session[key_];
}

public static void Set<T>(string key_, T value_)
{
    _session[key_] = value_;
}

public static Account Account
{
    get
    {
        return Get<Account>("Account");
    }
    set
    {
        Set<Account>("Account", value);
    }
}

login.aspx (in project/namespace/assembly Web)

protected void loginButton_Click(object sender, System.EventArgs e)
{
    DecoratedSession.Account = Global.Firewall.Login(tb_user.Text, tb_user.Text);
    Response.Redirect("main.aspx", true);
}

main.aspx (in project/namespace/assembly Web)

protected void Page_Load(object sender, System.EventArgs e)
{
    // Error thrown here, when accessing the "roles" set
    if (DecoratedSession.Account.HasPermission(Permission.FULL_ACCESS))
    {
        // ...
    }
}

I can even see in the logfiles that the session is openend, then the exception occurs, and then it is closed.

Global: 15:20:08,669 DEBUG Global - Opened session [529000] for request to [/main.aspx]
Global: 15:20:08,670 DEBUG Global - STARTREQUEST Session id: [529000]
main: 15:20:08,682 DEBUG main - MAIN Session id: [529000]
NHibernate.LazyInitializationException: 15:20:08,685 ERROR NHibernate.LazyInitializationException - Initializing[Core.Model.Account#2586]-failed to lazily initialize a collection of role: Core.Model.Account.Roles, no session or session was closed
NHibernate.LazyInitializationException: Initializing[Core.Model.Account#2586]-failed to lazily initialize a collection of role: Core.Model.Account.Roles, no session or session was closed
Global: 15:20:14,160 DEBUG Global - Closed session [529000] for request to [/main.aspx]

How can I debug this?

F.P
  • 17,421
  • 34
  • 123
  • 189

1 Answers1

3

The point here is (I did not dive into the code above, but based no my experience) related to static vs http request life cycles.

While there is open session, the object blaming for its session being closed comes from some previous (related to another web request)

Check some details here:

Solution I see in introducing some kind of Prototype pattern:

[Serializable]
public class Account, ICloneable, ...
{
    ...
    public override object Clone()
    {
        var entity = base.Clone() as Account;
        entity.Roles = ... (clone roles)
        ...
        return entity;
    }
    ...

And when loading Account (user, role) for Authentications, we should call Clone() to be sure that all important properties (many-to-one, one-to-many) were loaded and are later ready for use. That clone could be cached in static context

Community
  • 1
  • 1
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • But that can hardly be the correct way how to handle "logged in" sessions with NHibernate, right? I did the exact same thing in another web app and it worked fine there... – F.P Oct 15 '15 at 14:39
  • 1
    Honestly, all my security is always loaded once, cloned and cached. It is later compared against current user... – Radim Köhler Oct 15 '15 at 14:44
  • Okay, thanks for the input. I think I figured out why it works in my other application: In the one that works, after I get the `Account` from the databse, I immediately access the `Roles/Permissions` field, therefore forcing it to load into the session. I don't do that on the other application, hence the error... Can you explain how you "*clone and cache*" the security details? I think I need to do the same. – F.P Oct 15 '15 at 14:55
  • 1
    In the link I gave you should see more details. The trick of clone is - if the Clone method is called, the session is active. And anything which is touched (to be cloned as well) could be properly loaded during that session. So, If we store Accounts, which were cloned, they contain all the information (while mapped as LAZY - they are really populated). Later, anytime from any place (e.g. from other request ) we can ask these Static data for any info - and that will be there... Hope it helps a bit – Radim Köhler Oct 15 '15 at 14:57
  • 1
    Please, try to check this pattern to get more understanding why it exists and is really suitable here https://en.wikipedia.org/wiki/Prototype_pattern **... to avoid the inherent cost of creating a new object in the standard...** – Radim Köhler Oct 15 '15 at 14:57