与IQueryOver连接时的Nhibernate条件

时间:2011-12-08 12:20:50

标签: nhibernate subquery criteria queryover icriteria

我在google和stackoverflow上搜索类似的问题差不多2个小时,但没有找到任何解决方案。

我有两张关系1对多的表。

1) [Accounts]
PK Account_Id
int User_ID


2) [Temporary_Accounts]
Fk Account_Id
char IsAccepted   {'1','0',null}
varchar name

2个映射类

1) Acc
int Id;
User user;
TempAcc Temp; //cause each Account can have 0 or one TempAcc (with IsAccepted == null)

2)TempAcc
int Id;
bool IsAccepted;
string name;

我想显示给定user_id的所有帐户以及在[Temporary_Accounts]和IsAccepted == null中记录的帐户的附加信息(f.e名称)。

所以SQL应该是这样的:

select acc.Account_Id, acc.User_Id, tempacc.Name 

from Account acc left join Temporary_Account tempacc 
on (acc.Account_ID = tempacc.Account_Id and tempacc.IsAccepted is null)
where (acc.User_Id = 65);

但我的IQueryOverquery:

IQueryOver<Acc> query = (...)
query.JoinAlias(f => f.Temp,
                () => Temp,
                JoinType.LeftOuterJoin)
     .Where(f => f.Temp.IsAccepted == null)
     .And(f => f.user.id == userid);

生成这样的sql:

select acc.Account_Id, acc.User_Id, tempacc.Name 

from Accounts acc left join Temporary_Accounts tempacc 
on (acc.Account_ID = tempacc.Account_Id)
where (acc.User_Id = 65 and tempacc.IsAccepted is null);

所以我得到的结果比第一次正确的查询要少。

您有什么想法我应该更改什么或我该怎么做以从第一次查询获得结果?我的想法是使用子查询leftjoin Accounts表,它从Temporary_Accounts表中选择所有IsAccepted = null帐户,但我不知道如何在Iqueryover或Icriteria中执行此操作。

我会很感激任何建议

1 个答案:

答案 0 :(得分:6)

由于您在AccTemp之间有1-Many,因此您的示例sql将生成笛卡尔积。

您需要的查询使用子查询,看起来如下所示:

Acc accountAlias = null;
var subQuery = QueryOver.Of<Temp>()
               .Where(x=>x.IsAccepted==null)
               .And(x=>x.Account.Id==accountAlias.Id);

var results = session.QueryOver<Acc>(()=>accountAlias)
              .Where(x=>x.User.Id==65)
              .WithSubquery.WhereExists(subQuery);

像这样生成SQL:

select *
from Accounts a
where a.User_Id=65
and exists (
    select t.Account_Id
    from Temporary_Accounts t
    where t.IsAccepted is null and t.Account_Id=a.Account_Id
)

nhibernate.info上的这篇文章对于使用QueryOver计算复杂查询非常有帮助。

更新:

如果您还需要找到Accounts中没有任何相应行的Temporary_Accounts,那么您需要两个subqueries and a Disjunction

Acc accountAlias = null;
var hasTempAccount = QueryOver.Of<Temp>()
               .Where(x=>x.IsAccepted==null)
               .And(x=>x.Account.Id==accountAlias.Id);

var doesNotHaveTempAccount = QueryOver.Of<Temp>()
               .And(x=>x.Account.Id==accountAlias.Id);

var results = session.QueryOver<Acc>(()=>accountAlias)
  .Where(x=>x.User.Id==65)
  .Where(Restrictions.Disjunction()
    .Add(Subqueries.WhereExists(hasTempAccount))
    .Add(Subqueries.WhereNotExists(doesNotHaveTempAccount))     
  );

更新2:

自NH 3.2起,您可以为JOIN添加额外条件。有关详细信息,请参阅此答案:Adding conditions to outer joins with NHibernate ICriteria/QueryOver query

Temp tempAlias = null;
Account accountAlias = null;
dto dto = null;
var results = Session.QueryOver<Account>(()=>accountAlias)
  .JoinAlias(x=>x.TempAccounts,()=>tempAlias,JoinType.LeftOuterJoin,
    Restrictions.IsNull(Projections.Property(()=>tempAlias.IsAccepted))
  )
  .Where(x=>x.Account.Id==65)
  .SelectList(list=>list
      .Select(()=>accountAlias.Id).WithAlias(()=>dto.AccountId)
      .Select(()=>accountAlias.User.Id).WithAlias(()=>dto.UserId)
      .Select(()=>tempAlias.Name).WithAlias(()=>dto.TempAccName)
  )
  .SetResultTransformer(Transformers.AliasToBean<dto>())
  .List<dto>();