nhibernatenhibernate-mappingnhibernate-criteria

NHibernate multiple inner join select


I'm trying to get NHibernate to do a simple query based on the inner joins of 3 tables:

var sessionCriteria = session.CreateCriteria<FoobarMaster>("M")
.CreateCriteria("Accounts", "A", NHibernate.SqlCommand.JoinType.InnerJoin)
.CreateCriteria("TrackingRecords", "T", NHibernate.SqlCommand.JoinType.InnerJoin)
.Add(Restrictions.Eq("T.PicNumber", "123456"));
var foobarMaster = sessionCriteria.UniqueResult<FoobarMaster>();

And the same thing in LINQ:

from m in session.Query<FoobarMaster>()
from a in m.Accounts
from t in a.TrackingRecords
where t.PicNumber == "12345"
select m

I have the same thing using QueryOvers and JoinAliases. For all, I'm getting a runtime exception:

"could not resolve property: TrackingNo of: Account".

It is strange since TrackingNo is a TrackingRecord property and not an Account property. It is even prefixed by T - the alias for TrackingRecord.

Here is my mapping:

<class name="FoobarMaster" table="T_FOOBAR_MASTER">
 <id name="FoobarMasterId" column="FOOBAR_MASTER_ID" type="int">
   <generator class="identity"/></id>
 <bag name="Accounts" cascade="all" inverse="true">
   <key column="FOOBAR_MASTER_ID" />
   <one-to-many class="FoobarAccount" />
 </bag>
...

<class name="FoobarAccount" table="T_FOOBAR_ACCOUNT">
 <id name="FoobarAccountId" column="FOOBAR_ACCOUNT_ID" type="int">
   <generator class="identity"/></id>
 <many-to-one name="FoobarMaster" class="FoobarMaster" column="FOOBAR_MASTER_ID" />
 <property name="AccountId" column="ACCOUNT_ID" />
 <bag name="TrackingRecords" cascade="all" inverse="true">
   <key column="ACCOUNT_ID" />
   <one-to-many class="FoobarAccount" />
 </bag>
...

<class name="TrackingRecord" table="T_TRACKING">
 <id name="TrackingId" column="TRACKING_ID" type="int"><generator class="identity"/></id>
 <many-to-one name="FoobarAccount" class="FoobarAccount" column="ACCOUNT_ID" />
 <property name="PicNumber" column="PICNUMBER" type="AnsiString" length="25" />
 ...

Here are classes/Entities:

public class FoobarMaster
{
 public virtual int FoobarMasterId { get; set; }
 public virtual IList<FoobarAccount> Accounts { get; set; }
...

public class FoobarAccount
{
 public virtual int FoobarAccountId { get; set; }
 public virtual FoobarMaster FoobarMaster { get; set; }
 public virtual int AccountId { get; set; }
 public virtual IList<TrackingRecord> TrackingRecords { get; set; }
...

public class TrackingRecord
{
 public virtual long TrackingId { get; set; }
 public virtual FoobarAccount FoobarAccount { get; set; }
 public virtual string PicNumber { get; set; }
...

Solution

  • The real issue

    The answer here, based on the latest question updated, is clear and easy to fix! The mapping contains a wrong one-to-many setting. See the first level list:

    <bag name="Accounts" cascade="all" inverse="true">
      <key column="FOOBAR_MASTER_ID" />
      <!-- here we can see the CORRECT reference -->
      <one-to-many class="FoobarAccount" />
    </bag>
    

    On the other hand, the second level is having the same target, which is wrong:

    <bag name="TrackingRecords" cascade="all" inverse="true">
      <key column="ACCOUNT_ID" />
      <!--  WRONG. In deed, the Account does NOT contain 'PicNumber' -->
      <one-to-many class="FoobarAccount" />
    </bag>
    

    The answer:

    Change the <one-to-many class="FoobarAccount" />
    into <one-to-many class="TrackingRecord" />

    Correct mapping should be like this:

    <bag name="TrackingRecords" cascade="all" inverse="true">
      <key column="ACCOUNT_ID" />
      <!--  now we won't recieve the Account does not contain 'PicNumber'  -->
      <one-to-many class="TrackingRecord" />
    </bag>
    

    From that moment, all the stuff will work properly, the query in the begining of the question is correct. No need for subqueries etc

    ...

    Original tips - related to previously available information

    What you are trying achieve would work if your object/entities would be chained like this

    1. Master has-many (or references) Accounts
    2. Account has-many (or references) TrackingRecords

    But based on the issue you have, it seems that your mapping is

    1. Master has-many (or references) Accounts
    2. Master has-many (or references) TrackingRecords

    In that case, you can only achieve SQL Like this

    select m.* from t_master m
    inner join t_account a on m.master_id = a.master_id
    //inner join t_tracking t on a.account_id = t.account_id
    inner join t_tracking t on m.account_id = t.account_id // the m.account_id
    where t.tracking_no = '123456'
    

    And the query should be like this:

    // do some filter over A
    var rootQuery = session.CreateCriteria<Master>("M")
                           .CreateCriteria("Accounts", "A", NHibernate.SqlCommand.JoinType.InnerJoin);
    
    // working with the T here
    rootQuery.CreateCriteria("TrackingRecords", "T", NHibernate.SqlCommand.JoinType.InnerJoin)
             .Add(Restrictions.Eq("T.TrackingNo", "123456"));
    

    Update, reflecting the question extension:

    This mapping does not fit together:

    The key column of the TrackingRecords is ACCOUNT_ID

    <class name="Account" table="T_ACCOUNT"> 
    ...
    <bag name="TrackingRecords" cascade="all" inverse="true">
      <key column="ACCOUNT_ID" />
    ...
    

    While the reference from TrackingRecord is done via ACCOUNT_NUMBER

    <class name="TrackingRecord" table="T_TRACKING">
    ...
    <many-to-one name="Account" class="Account" column="ACCOUNT_NUMBER" />
    ...