使用nHibernate按子集合限制QueryOver

本文关键字:QueryOver 子集合 nHibernate 使用 | 更新日期: 2023-09-27 18:27:02

我正在尝试获取一个父实体,其中子集合中的所有实体都在另一个列表中。

例如:

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

我尝试过各种Joins和Restrictions的组合,但似乎都不到位。

因此,请提供建议。

下面的当前示例:

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

编辑:

这是sql等价物:

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

使用nHibernate按子集合限制QueryOver

这是我的建议:

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

其思想如下:查找具有类似于names条目之一的Name的所有子项。根据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 */
    );

对于我的测试场景来说,它是有效的,所以希望它也适用于您。