Can I reattach a collection in NHibernate?
-
28-06-2021 - |
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;
}
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 );
}