【问题标题】:NHibernate multiple inner join selectNHibernate 多重内连接选择
【发布时间】:2014-01-21 08:52:50
【问题描述】:

我正在尝试让 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 属性而不是 Account 属性。它甚至以 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; }
...

【问题讨论】:

  • 您的 LINQ 版本运行正常吗?你能发布你的映射吗
  • LINQ 版本给出了同样的错误。我在上面发布了它,我还发布了映射。谢谢。
  • 我认为没有像 Erik 所描述的分离查询是不可能的。我发现了以下内容:blogs.taiga.nl/martijn/2008/11/20/…。我很失望。你可以毫不费力地在 Ruby on Rails 中做到这一点......
  • @MrTibs 只是出于好奇。你看到我更新的答案了吗?你的映射是错误的,因为TrackingRecords 有一对多的&lt;one-to-many class="FoobarAccount" /&gt;。这就是问题。还是您问题中的 sn-p 仍然“已调整”?换句话说,您尝试执行的查询是正确的。我确实测试过,一旦映射正确......我只是好奇;);)

标签: nhibernate nhibernate-mapping nhibernate-criteria


【解决方案1】:

真正的问题

这里的答案基于更新的最新问题,清晰且易于修复!映射包含错误的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>

答案:

更改&lt;one-to-many class="FoobarAccount" /&gt;
进入 &lt;one-to-many class="TrackingRecord" /&gt;

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

<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 has-many(或引用)帐户
  2. Account has-many(或引用)TrackingRecords

但根据您遇到的问题,您的映射似乎是

  1. Master has-many(或引用)帐户
  2. Master has-many(或引用)TrackingRecords

那样的话,只能实现SQL这样的

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

【讨论】:

  • 感谢 Radim,但我的设置就像您在前一种情况中提到的那样。 Master 有很多 Accounts,Account 有很多 TrakcingRecords。我编辑了我的帖子并添加了类和映射。
  • 我检查了你的映射,至少有一处不一致。请检查一下
  • 感谢您的关注。我确实有正确的映射(或者我认为正确的映射),但是当我粘贴我的代码时我不得不更改一些变量的名称,因为它们包含产品的名称并且它是商标。我编辑了代码。
  • 我真的很想帮忙,但现在我不确定什么是正确的映射,什么是模糊的部分。因为最新的映射很奇怪“ACCOUNT_NUMBER”是你的TrackingRecords集合的一个键列,但这不是映射id列名。摘要:您拥有的查询在通常情况下会起作用。我确实测试过了。如果链映射正确,则查询将起作用。除非我会找到真正的代码,否则我可以像我现在所做的那样猜测。对不起,我想帮忙,但这样是不可能的
  • 再次感谢您的帮助。我粘贴了确切的代码并将商标字符串替换为“foobar”。
【解决方案2】:

由于您仅通过 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 只有一个 Account 和 Master,那么 join fetch 可能是更好的选择。

【讨论】:

  • @MrTibs:大多数 DBMS 将子选择转换为半连接,这些可以通过嵌套查询(就像普通的内部连接一样)或其他算法在内部完成,具体取决于优化器选择的内容。如果您每个 TrackingNo 恰好有 1 个帐户和主控,这很有可能而且一点也不差。优点是:你不选择孩子,如果不同的加入路径导致你不会得到多个相等的主人。
  • 我让它与分离查询一起工作,但我讨厌代码的外观。我认为 ORM 的目的是简化事情,而不是让事情变得更难。不是我公司的每个人都是 NHibernate 专家。如果我把那个代码留在里面,他们就不会知道我在做什么。我最终编写了原始 SQL。还是谢谢。
猜你喜欢
  • 2012-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多