Question

I have objects of which some have Lists of Documents, other Objects, ... whatever. I do not want to use lazy=false on the relationship because it makes the application very slow. I can reattach objects with Lock, so other properties or one-to-many relationships load when they should. But for collections I always get "could not initialize a collection" if I try to access them. It does not work if I call Lock(obj) on the object that is connected to that location.

I want my mapping to look like this:

<set name="DocumentList" table="material_document" cascade="all" lazy="true">
  <key column="material_id"/>
  <many-to-many class="Document">
    <column name="document_id"/>
  </many-to-many>
</set>

Is there a method to reattach? Or is there a mapping setting?

update1:

this is the mapping on the material side:

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
<class name="Material" table="material">
<!-- Defining PK -->
<id name="Id" type="integer" column="id">
    <generator class="sequence">
      <param name="sequence">material_id_seq</param>
    </generator>
</id>
<!-- Defining Properties -->
<!-- Defining FK-Relations -->
<set name="DocumentList" table="material_document" cascade="all" lazy="false">
  <key column="material_id"/>
  <many-to-many class="Document">
    <column name="document_id"/>
  </many-to-many>
</set>

The Document mapping file does not have any information about the relationship. The database name is alright, everything else works. The tables are called material, material_document and document. My classes are public and the properties public virtual. What can be wrong with my entities? Do you mean something with the data is wrong or something could have happened to the objects in the program...?

List material = LoadAllMaterials();
//some code, that causes the session to be closed, a new session will be opened
foreach (Document d in material.DocumentList) { //causes the exception
    //do something
}

Before that, I want to fetch the documents, without loading them beforehand.

update 2: how I currently reattach objects: I set force true when I know there is a proxy and it doesn't work another way... And I have to check if it's in the session because evict throws an Exception if I call it on an object that is not in the session.

public void Reattach(Type type, BaseObject o, bool force)
{
    bool inSession = o.IsInSession();

    if (force && inSession)
    {
        GetSession().Evict(o);
        GetSession().Lock(type.ToString(), o, LockMode.None);
    }
    else {

        o = (BaseObject)GetSession().GetSessionImplementation().PersistenceContext.Unproxy(o);

        if (!inSession) {

            GetSession().Lock(type.ToString(), o, LockMode.None);
        }
    }
}

and this is my IsInSession() method:

public virtual bool IsInSession() {

    ISession session = HibernateSessionManager.Instance.GetSession();

    var pers = session.GetSessionImplementation()
          .GetEntityPersister(GetType().ToString(), this);

    NHibernate.Engine.EntityKey key = new NHibernate.Engine.EntityKey(Id,
        pers, EntityMode.Poco);

    bool isInSession = false;

    try
    {
        object entity = session.GetSessionImplementation().PersistenceContext.GetEntity(key);

        if (entity != null)
        {
            isInSession = true;
        }
    }
    catch (NonUniqueObjectException) {}

    return isInSession;
}
Was it helpful?

Solution

I do see that you have lazy="false" in one of your mappings...NHibernate uses lazy loading by default so I would remove all of the lazy="false" and lazy="true" statements from your mappings and just go with the NHibernate defaults.

I created the following NUnit tests and both tests pass so I wasn't able to duplicate your issue which means it's either your lazy="false" mapping stuff or some other issue... it's always difficult to diagnose these issues without have the full application in front of you...

Using the following test case, I was able to create the following error: "Initializing[SampleApplication.Customer#19]-failed to lazily initialize a collection of role: SampleApplication.Customer.Addresses, no session or session was closed"

[Test]
public void Testing_A_Detached_Entity()
{
    // Arrange
    var sessionFactory = ObjectFactory.GetInstance<ISessionFactory>();

    Customer customer = null;

    using ( ISession session = sessionFactory.OpenSession() )
    {
        using ( ITransaction tx = session.BeginTransaction() )
        {
            customer = session.Query<Customer>()
                .Where( x => x.CustomerNbr == 19 )
                .FirstOrDefault();
        }
    }

    // Act
    TestDelegate actionToPerform = () =>
       {
           // Looping over this child collection should throw an Exception
           // because we no longer have an active NHibernate session
           foreach ( var address in customer.Addresses )
           {

           }
       };

    // Assert
    Assert.Throws<NHibernate.LazyInitializationException>( actionToPerform );
}

Using the following test case and using NHibernate's session.Lock() method, I was able to successfully reattach the detached object and obtain a count on my child collection.

[Test]
public void Testing_Reattaching_A_Detached_Entity()
{
    // Arrange
    var sessionFactory = ObjectFactory.GetInstance<ISessionFactory>();

    Customer customer = null;

    using ( ISession session = sessionFactory.OpenSession() )
    {
        using ( ITransaction tx = session.BeginTransaction() )
        {
            customer = session.Query<Customer>()
                .Where( x => x.CustomerNbr == 19 )
                .FirstOrDefault();
        }
    }

    // Act
    ISession newSession = sessionFactory.OpenSession();

    newSession.Lock( customer, LockMode.None );

    // Assert
    Assert.That( customer.Addresses.Count > 0 );
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top