2012-03-06 85 views
3

我想獲得一個父實體,其中在一個子集合中的所有實體都在另一個列表中。通過使用nHibernate的子集合限制QueryOver

例如:

public class Parent { 

public virtual int Id {get;set;} 
public virtual List<Child> Children {get;set;} 

} 

public class Child { 

public virtual int Id {get;set;} 
public virtual string Name {get;set;} 

} 

我已經試過各種組合的聯接和限制,但似乎無法擊中當場。

所以請有幫助的建議。

下面

當前的例子:

public IList<Lead> GetAllAvailable(string[] names) 
    { 
     var result = Session.CreateCriteria<Parent>() 
      .CreateCriteria("Children") 
      .Add(Expression.In("Name", names)).List<Parent>(); 

     return result; 
    } 

編輯:

這是SQL equivilent:

select * 
from dbo.Parent 
     join (select p.id 
       from  dbo.Parent p 
         join dbo.ParentToChildren on p.Id = dbo.ParentsToChildren.Parent_Id 
         join dbo.Child on dbo.ParentToChildren.Child_Id = dbo.Child.Id 
       where Name in ('foo', 'bar') 
       group by p.Id 
       having count(1) > 1 
      ) as foo on dbo.Parent.Id = foo.Id 
+0

要清楚,孩子們應該匹配列表中的每個項目。 – shenku 2012-03-06 22:38:23

+0

你是否想要所有父母都有姓名的正確名稱?長兒童,哪裏有一個名字的兒童(名稱中包含該條目)? – 2012-03-06 23:02:05

+0

不,他們可以有更多的,所以父母可以有10個孩子,但只需要匹配名單中的3。 (例如) – shenku 2012-03-06 23:03:58

回答

3

這裏是我的建議:

var parents = session.QueryOver<Child>() 
    .WhereRestrictionOn(x => x.Name).IsIn(names) 
    .Select(Projections.Group<Child>(x => x.Parent)) 
    .Where(Restrictions.Ge(Projections.Count<Child>(x => x.Parent), names.Length)) 
    .List<Parent>(); 

的想法是如下:找到所有的孩子有Namenames條目之一。通過Parent將這些孩子分組。你的Child將需要一個Parent屬性映射到各自的父母爲此,但這是一個好主意無論如何。對於大小等於(或大於,但不應該發生,所以你可以用Eq代替Genames.Length的所有組,返回它們的父代;因爲如果該組的大小等於names.Length,則所有名稱都被發現假設父母的兩個孩子沒有相同的名字

生成的查詢:

SELECT 
    this_.Parent as y0_ 
FROM 
    Child this_ 
WHERE 
    this_.Name in (
     /* */ 
    ) 
GROUP BY 
    this_.Parent 
HAVING 
    count(this_.Parent) >= /* names.Length */; 

我創建了返回希望的結果一個測試應用程序。

如果你需要對父母做更多的事情,比如分頁或者抓取孩子,你可以將這個問題分解爲一個子查詢(請注意,.Fetch(x=>x.Children).Eager行不是必需的,這只是一個例子,你可以繼續使用查詢):

var parentSubQuery = 
    QueryOver.Of<Child>() 
    .WhereRestrictionOn(x => x.Name).IsIn(names) 
    .Select(Projections.Group<Child>(x => x.Parent)) 
    .Where(Restrictions.Ge(Projections.Count<Child>(x => x.Parent), names.Length)); 

var parents = session.QueryOver<Parent>() 
    .Fetch(x=>x.Children).Eager // not necessary, just an example 
    .WithSubquery.WhereProperty(x => x.Id).In(parentSubQuery) 
    .List(); 

SQL(不Fetch):

SELECT 
    this_.Id as Id1_0_ 
FROM 
    Parent this_ 
WHERE 
    this_.Id in (
     SELECT 
      this_0_.Parent as y0_ 
     FROM 
      Child this_0_ 
     WHERE 
      this_0_.Name in (
       /* names */ 
      ) 
     GROUP BY 
      this_0_.Parent 
     HAVING 
      count(this_0_.Parent) >= /* names.length */ 
    ); 

更新:

如果家長< - >孩子多到多,事情變得有些棘手:

 Parent parent = null; 
     var parentSubQuery = QueryOver.Of<Child>() 
     .WhereRestrictionOn(x => x.Name).IsIn(names) 
     .JoinQueryOver(x => x.Parents,() => parent) 
     .Where(Restrictions.Ge(Projections.Count(() => parent.Id), names.Length)) 
     .Select(Projections.Group(() => parent.Id)); 

     var parents = session.QueryOver<Parent>() 
     .WithSubquery.WhereProperty(x => x.Id).In(parentSubQuery) 
     .List(); 

的主要區別在於不是通過Child直接Parent屬性分組我首先需要參加家長集合。爲了引用每個父母,我引入了別名parent

生成的SQL是非常接近原來的做法:

SELECT 
    this_.Id as Id2_0_ 
FROM 
    Parent this_ 
WHERE 
    this_.Id in (
     SELECT 
      parent1_.Id as y0_ 
     FROM 
      Child this_0_ 
     inner join 
      ChildToParent parents3_ 
       on this_0_.Id=parents3_.ChildId 
     inner join 
      Parent parent1_ 
       on parents3_.ParentId=parent1_.Id 
     WHERE 
      this_0_.Name in (
       /* names */ 
      ) 
     GROUP BY 
      parent1_.Id 
     HAVING 
      count(parent1_.Id) >= /* names.Length */ 
    ); 

對於我的測試場景中它的作品,所以希望它會爲你。

+0

aw man,那真是太棒了,只會影響你的查詢的一件事是,一個孩子可以有許多父母,而不僅僅是一個。所以'.Select(Projections.Group (x => x.Parent.Id))'不會編譯?謝謝! – shenku 2012-03-06 23:43:30

+0

對不起,我已經睡了。家長<->兒童是多對多的,這使得NH難以做到。我需要測試一下。敬請關注:-) – 2012-03-07 07:33:26

+0

我更新了我的答案以處理多對多問題。 – 2012-03-07 08:38:27