NHibernate多个内连接选择

时间:2014-01-21 08:52:51

标签: nhibernate nhibernate-mapping nhibernate-criteria

我试图让NHibernate基于3个表的内部联接进行简单的查询:

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>();

在LINQ中也是如此:

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

我使用QueryOvers和JoinAliases做同样的事情。对于所有人,我得到一个运行时异常:

  

“无法解析财产:TrackingNo of:Account”。

奇怪的是,TrackingNoTrackingRecord属性,而不是帐户属性。它甚至以T为前缀 - TrackingRecord的别名。

这是我的映射:

<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" />
 ...

以下是课程/实体:

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; }
...

2 个答案:

答案 0 :(得分:3)

真正的问题

这里的答案基于最新更新的问题,清晰易懂!映射包含错误的one-to-many设置。请参阅第一级列表:

<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>

另一方面,第二级具有相同的目标,这是错误的:

<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>

答案:

更改<one-to-many class="FoobarAccount" />
进入 <one-to-many class="TrackingRecord" />

正确的映射应该是这样的:

<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>
  

从那一刻起,所有的东西都会正常工作,问题开头的查询是正确的。不需要子查询等

...

原始提示 - 与以前提供的信息相关

如果您的对象/实体将被链接到这样的

,那么您正在尝试实现的功能将起作用
  1. Master有多个(或参考)帐户
  2. Account有多个(或参考) TrackingRecords
  3. 但根据您的问题,您的映射似乎是

    1. Master有多个(或参考)帐户
    2. Master有多个(或参考) TrackingRecords
    3. 在这种情况下,你只能实现像这样的

      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'
      

      查询应该是这样的:

      // 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"));
      

      更新,反映问题扩展名:

      此映射不适合:

      TrackingRecords的关键列是 ACCOUNT_ID

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

      虽然TrackingRecord的参考是通过 ACCOUNT_NUMBER

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

答案 1 :(得分:0)

由于您只通过SQL选择主数据,没有来自T_Account和T_TrackingRecord的字段/列,我建议使用DetachedCriteria和子查询。

如果你有TrackingRecord.AccountId和Account.MasterId属性,你可以这样做:

var trackRecAccountIdsSubq = DetachedCriteria.For(typeof(TrackingRecord))
                .SetProjection(Projections.Property("AccountId"))
                .Add(
                    Restrictions.Eq("TrackingNo", "123456")); 

var accountMasterIdsSubq = DetachedCriteria.For(typeof(Account))
                .SetProjection(Projections.Property("MasterId"))
                .Add(
                    Subqueries.PropertyIn("AccountId", trackRecAccountIds)); 

var mastersByTrackRecNoCriteria = session.CreateCriteria<Master>()
                .Add(
                    Subqueries.PropertyIn("MasterId", accountMasterIdsSubq);

var mastersByTrackRecNo = mastersByTrackRecNoCriteria.List<Master>();

生成的SQL将类似于:

Select * from t_master where master_id in (
    select master_id from t_account where account_id in (
        select account_id from t_tracking where tracking_no=123456
    )
)

不确定它是否以及如何使用实体对象属性(没有外部ID属性),但子查询在数据库上创建快速半连接(理想情况下通过父实体ID的索引)。

默认情况下,这也只会获取Master,而不是Account或TrackingRecord;如果你不需要它们,那就好又快。如果您需要它们,并且每个TrackingNo只有一个帐户和主服务器,则连接提取可能是更好的选择。