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; }
...
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>
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
...
What you are trying achieve would work if your object/entities would be chained like this
Master
has-many (or references) AccountsAccount
has-many (or references) TrackingRecordsBut based on the issue you have, it seems that your mapping is
Master
has-many (or references) AccountsMaster
has-many (or references) TrackingRecordsIn 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"));
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" />
...