I'm having problems with parent-child relationships on entities that have composite ids. When a parent is loaded, its children are not added to the child-collection. The funny part is that the children are retrieved from the database and instantiated.
In the database I have two simple tables: one for parents and one for children (see later). The parent table contains one row and the child table contains two rows that reference the parent row. When loading the parent, only the parent is fetched in the database and instantiated. When accessing the child-collection of the parent, the two children are fetched from the database and instantiated (I can see that because IInterceptor.Instantiate is invoked for both of them). But the child-collection is empty!
Can anyone explain this?
I have noticed that NHibernate initializes several collections according to the logger output:
Code:
1718 [2656] DEBUG NHibernate.Impl.SessionImpl (null) - initializing non-lazy collections
1734 [2656] DEBUG NHibernate.Impl.SessionImpl (null) - initializing collection [Base.Entities.TestParent.Children#Base.Entities.TestParentPK(ID1=11,ID2=12)]
...
1734 [2656] DEBUG NHibernate.Impl.SessionImpl (null) - uninitialized collection: initializing
...
1734 [2656] DEBUG NHibernate.Impl.SessionImpl (null) - new collection: instantiating
...
1843 [2656] DEBUG NHibernate.Impl.SessionImpl (null) - new collection: instantiating
...
1859 [2656] DEBUG NHibernate.Impl.SessionImpl (null) - 3 collections were found in result set
1859 [2656] DEBUG NHibernate.Impl.SessionImpl (null) - collection fully initialized: [Base.Entities.TestParent.Children#Base.Entities.TestParentPK(ID1=11,ID2=12)]
1859 [2656] DEBUG NHibernate.Impl.SessionImpl (null) - collection fully initialized: [Base.Entities.TestParent.Children#Base.Entities.TestParentPK(ID1=11,ID2=12)]
1859 [2656] DEBUG NHibernate.Impl.SessionImpl (null) - collection fully initialized: [Base.Entities.TestParent.Children#Base.Entities.TestParentPK(ID1=11,ID2=12)]
1859 [2656] DEBUG NHibernate.Impl.SessionImpl (null) - 3 collections initialized
Is this correct?Also, if one of the children are loaded directly afterwards, it is fetched from the database again and a new object is instantiated.
Should this object not be in the cache already?Thanks,
Stig Irming-Pedersen
Documentation:Setup:
Code:
Database: MS SQL 2000
NHibernate: 1.0.2
.NET: 2.0
The parent and child tables:
Code:
TestParent(int id1, int id2, string name)
TestChild(int id1, int id2, string name, int parent_id1, parent_id2)
The following data is contained in the tables:
Code:
TestParent: (11, 12, 'p_name')
TestChild: (16, 22, 'c1', 11, 12) and (16, 33, 'c2', 11, 12)
Mapping file for the parent:
Code:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="Base.Entities.TestParent, Base" table="TestParent" lazy="true" >
<composite-id name="PK" class="Base.Entities.TestParentPK, Base" access="nosetter.lowercase" >
<key-property name="ID1" column="id1" type="Int32" access="nosetter.lowercase" />
<key-property name="ID2" column="id2" type="Int32" access="nosetter.lowercase" />
</composite-id>
<property name="Name" column="name" type="String" length="10" access="property" />
<bag name="Children" access="property" inverse="true" cascade="none" lazy="true" fetch="join" table="TestChild">
<key>
<column name="parent_id1" />
<column name="parent_id2" />
</key>
<one-to-many class="Economic.API.Base.Entities.TestChild, Economic.API.Base" />
</bag>
</class>
</hibernate-mapping>
Mapping file for the child:
Code:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="Base.Entities.TestChild, Base" table="TestChild" lazy="true">
<composite-id name="PK" class="Base.Entities.TestChildPK, Base" access="nosetter.lowercase" >
<key-property name="ID1" column="id1" type="Int32" access="nosetter.lowercase" />
<key-property name="ID2" column="id2" type="Int32" access="nosetter.lowercase" />
</composite-id>
<property name="Name" column="name" type="String" length="10" access="property" />
<many-to-one name="Parent" class="Base.Entities.TestParent, Base" access="nosetter.camelcase" not-null="true">
<column name="parent_id1" />
<column name="parent_id2" />
</many-to-one>
</class>
</hibernate-mapping>
Entity class and primary key class for the parent:
Code:
public class TestParent
{
public TestParent()
{
}
private TestParentPK pk = null;
public virtual TestParentPK PK
{
get { return pk; }
set { pk = value; }
}
private string name = null;
public virtual string Name
{
get { return name; }
set { name = value; }
}
private IList children = null;
public virtual IList Children
{
get { return children; }
set { children = value; }
}
public static TestParent FindByNumber(ISession nHibernateSession, int id1, int id2)
{
IList result = nHibernateSession.Find("from TestParent as parent where parent.PK.ID1 = :id1 and parent.PK.ID2 = :id2", new object[] { id1, id2 } , new IType[] { NHibernateUtil.Int32, NHibernateUtil.Int32 });
if (result.Count > 0 && result[0] != null)
{
return (TestParent)result[0];
}
else
{
return null;
}
}
}
public class TestParentPK
{
private int id1;
private int id2;
public TestParentPK()
{
}
public TestParentPK(int id1, int id2)
{
this.id1 = id1;
this.id2 = id2;
}
public virtual int ID1
{
get { return id1; }
}
public virtual int ID2
{
get { return id2; }
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
TestParentPK other = (TestParentPK)obj;
bool result = (this.ID1 == other.ID1) && (this.ID2 == other.ID2);
Console.WriteLine("TestParentPK.Equals invoked: " + result);
return result;
}
public override int GetHashCode()
{
return base.GetHashCode() ^ ID1 ^ ID2;
}
public override string ToString()
{
return GetType().ToString() + "(ID1=" + ID1 + ",ID2=" + ID2 + ")";
}
}
Entity class and primary key class for the child:
Code:
public class TestChild
{
public TestChild()
{
}
private TestChildPK pk = null;
public virtual TestChildPK PK
{
get { return pk; }
set { pk = value; }
}
private string name = null;
public virtual string Name
{
get { return name; }
set { name = value; }
}
private TestParent parent;
public virtual TestParent Parent
{
get { return parent; }
}
public static TestChild FindByNumber(ISession nHibernateSession, int id1, int id2)
{
IList results = nHibernateSession.Find("from TestChild as child where child.PK.ID1 = ? and child.PK.ID2 = ?", new object[] { id1, id2 }, new IType[] { NHibernateUtil.Int32, NHibernateUtil.Int32 });
if (results.Count > 0)
{
return (TestChild)results[0];
}
else
{
return null;
}
}
}
public class TestChildPK
{
private int id1;
private int id2;
public TestChildPK()
{
}
public TestChildPK(int id1, int id2)
{
this.id1 = id1;
this.id2 = id2;
}
public virtual int ID1
{
get { return id1; }
}
public virtual int ID2
{
get { return id2; }
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
TestChildPK other = (TestChildPK)obj;
bool result = (this.ID1 == other.ID1) && (this.ID2 == other.ID2);
return result;
}
public override int GetHashCode()
{
return base.GetHashCode() ^ ID1 ^ ID2;
}
public override string ToString()
{
return GetType().ToString() + "(ID1=" + ID1 + ",ID2=" + ID2 + ")";
}
}
Code executed:
Code:
TestParent parent = TestParent.FindByNumber(nHibernateSession, 11, 12);
Console.WriteLine("Parent name: " + parent.Name);
Console.WriteLine("Children count: " + parent.Children.Count); // Returns 0!!!
TestChild child = TestChild.FindByNumber(nHibernateSession, 16, 22);
Console.WriteLine("Child name: " + child.Name);
SQL statements executed:
Code:
exec sp_executesql N'select testparent0_.id1 as id1, testparent0_.id2 as id2, testparent0_.name as name from TestParent testparent0_ where (testparent0_.id1=@p0)and(testparent0_.id2=@p1)', N'@p0 int,@p1 int', @p0 = 11, @p1 = 12
exec sp_executesql N'SELECT children0_.parent_id1 as parent_id1__, children0_.parent_id2 as parent_id2__, children0_.id1 as id1__, children0_.id2 as id2__, children0_.id1 as id10_, children0_.id2 as id20_, children0_.name as name0_, children0_.parent_id1 as parent_id10_, children0_.parent_id2 as parent_id20_ FROM TestChild children0_ WHERE children0_.parent_id1=@p0 and children0_.parent_id2=@p1', N'@p0 int,@p1 int', @p0 = 11, @p1 = 12
exec sp_executesql N'select testchild0_.id1 as id1, testchild0_.id2 as id2, testchild0_.name as name, testchild0_.parent_id1 as parent_id1, testchild0_.parent_id2 as parent_id2 from TestChild testchild0_ where (testchild0_.id1=@p0)and(testchild0_.id2=@p1)', N'@p0 int,@p1 int', @p0 = 16, @p1 = 22
Output:
Code:
IInterceptor.Instantiate(Base.Entities.TestParent,Base.Entities.TestParentPK(ID1=11,ID2=12)) called
Parent name: p_name
IInterceptor.Instantiate(Base.Entities.TestChild,Base.Entities.TestChildPK(ID1=16,ID2=22)) called
IInterceptor.Instantiate(Base.Entities.TestChild,Base.Entities.TestChildPK(ID1=16,ID2=33)) called
Children count: 0
IInterceptor.Instantiate(Base.Entities.TestChild,Base.Entities.TestChildPK(ID1=16,ID2=22)) called