-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 
Author Message
 Post subject: Composite ids + parent-child: child-collection always empty
PostPosted: Fri Apr 07, 2006 8:22 am 
Newbie

Joined: Fri Apr 07, 2006 5:21 am
Posts: 2
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


Top
 Profile  
 
 Post subject: Bad GetHashCode() implementation
PostPosted: Sun Apr 16, 2006 11:09 am 
Newbie

Joined: Sat Apr 15, 2006 2:02 pm
Posts: 1
Hello Stig.

The problem lies in the implementation of GetHashCode() in the TestParentPK-class (and also TestChildPK although that does not cause problems in your test code).

Your code looks like this:

Code:
public class TestChildPK
{
      ....
      public override int GetHashCode()
      {
         return base.GetHashCode() ^ ID1 ^ ID2;
      }
      .....
}


By convention GetHashCode () MUST return the same hashcode when two instances "Equals()". But base.GetHashCode() returns a random'ish number for each instance of TestChildPK.

These entity keys are stored in a Hashtable somewhere in NHibernate. But when two different instances contains the same value, but has different hashcodes, then a lookup might wrongly return null, even though the key *IS* present in the hashtable.

I tried replacing the GetHashCode() with something simple like:

Code:
public override int GetHashCode()
{
    return ID1 ^ ID2;
}

and it seems to work fine. This might not be the best hash-function, but at least it works.

Regards,
Ole Hyldahl Hansen


Top
 Profile  
 
 Post subject: Problem solved
PostPosted: Wed Apr 19, 2006 4:26 am 
Newbie

Joined: Fri Apr 07, 2006 5:21 am
Posts: 2
Hi Ole,

Thanks a lot, that solved the problem.


Best regards,
Stig Irming-Pedersen


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.