2012-03-06 84 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