【问题标题】:Express query with multiple OR and filter on joined table with Entity Framework Core使用多个 OR 表示查询并使用 Entity Framework Core 对连接表进行过滤
【发布时间】:2020-06-14 12:05:29
【问题描述】:

这是我的模型

class Parent 
{ 
   int Id; 
   string Name; 
   List<Child> Childs; 
} // name is unique

class Child 
{ 
    int Id; 
    int ParentId; 
    string Name; 
    Parent Parent; 
} // couple (id, name) is unique

对于给定的夫妻列表(父母姓名,孩子姓名),我想获得夫妻(父母,孩子),其中孩子可以为空,如果具有给定名称的父级存在,但子级不存在。 SQL 查询如下所示:

SELECT * 
FROM parents p
LEFT JOIN childs c ON c.parent_id = p.id
WHERE p.name = 'parent1' AND (c.name IS NULL OR c.name = 'child1')
   OR p.name = 'parent2' AND (c.name IS NULL OR c.name = 'child2')
   OR p.name = 'parent3' AND (c.name IS NULL OR c.name = 'child3')
   OR p.name = 'parent4' AND (c.name IS NULL OR c.name = 'child4');

我尝试在 Entity Framework Core 中使用 PredicateBuilder 的 Or 和 False 方法来表达这个查询

var predicate = PredicateBuilder.False<Parent>()
    .Or(p => p.Name == "parent1" && p.Childs.Any(c => c.Name == "child1"))
    .Or(p => p.Name == "parent2" && p.Childs.Any(c => c.Name == "child2"))
    .Or(p => p.Name == "parent3" && p.Childs.Any(c => c.Name == "child3"))
    .Or(p => p.Name == "parent4" && p.Childs.Any(c => c.Name == "child4"));

var p = await _db.Parents
    .Include(p => p.Childs)
    .Where(predicate)
    .ToArrayAsync();

这是我能得到的最接近的结果,但这并没有得到预期的结果:

  • 如果子级不存在,则父级不存在于结果集中
  • Parent.Childs 包含父级的所有子级,而不仅仅是想要的子级

我的查询可以用 Entity Framework Core 表达吗?

【问题讨论】:

  • 为什么 Child 有 ParentId 和 Parent?
  • 父级是navigation property。 ParentId 是可选的,但我认为它不会改变问题。
  • 但是看起来关系不好
  • 您是否意识到这是一个非常不常见的查询:给我父母和唯一的给定名字的孩子,或者没有任何孩子的父母并跳过只有孩子的父母有其他名字。特别是。最后一部分似乎不常见。这是故意的吗?
  • 好的,也就是说如果parent1有两个孩子child11child12,它就不会出现在列表中。

标签: c# sql entity-framework-core


【解决方案1】:

根据您的评论,现在的要求是:给我所有的父母,按姓名指定,每个父母只有一个特定的孩子,如果有的话。即:有 other 个孩子的父母会出现在结果中,但没有孩子。

这听起来很微不足道,但事实并非如此。问题是它需要两个过滤器,一个在父母身上,一个在孩子身上,其中孩子过滤器甚至是特定于父母的。 SQL 查询如下所示:

SELECT * 
FROM parents p1
LEFT JOIN 
(
    SELECT ch.*
    FROM children ch
    JOIN parents p2 ON ch.parentid = p2.id
    WHERE (p2.name = 'parent1' AND ch.name = 'child1')
       OR (p2.name = 'parent2' AND ch.name = 'child2')
       OR (p2.name = 'parent3' AND ch.name = 'child3')
       OR (p2.name = 'parent4' AND ch.name = 'child4') -- filter 2
) fc ON fc.parentid = p1.id
WHERE p1.name IN ('parent1','parent2','parent3','parent4') -- filter 1

对于 EF LINQ 查询,父谓词可以是简单的 Contains,但您可能希望使用谓词构建器构建谓词。在这里,出于后面的原因,我使用LINQkit.core

为了能够从一个来源构建谓词,我使用了一个临时结构(但我猜你已经有类似的东西了):

var filters = new[]
{
    new { ParentName = "parent1", ChildName = "child1" },
    new { ParentName = "parent2", ChildName = "child2" },
    new { ParentName = "parent3", ChildName = "child3" },
    new { ParentName = "parent4", ChildName = "child5" },
};

并准备谓词:

using LinqKit;
...
var parentNames = filters.Select(f => f.ParentName).ToList();
var childPredicateStarter = PredicateBuilder.New<Child>();
foreach (var filter in filters)
{
    childPredicateStarter = childPredicateStarter
        .Or(c => c.Parent.Name == filter.ParentName && c.Name == filter.ChildName);
}

现在,理想情况下,LINQ 查询看起来像这样(db 是一个上下文),解决了Include 中缺少过滤的问题:

var p = db.Parents
    .Where(p => parentNames.Contains(p.Name))
    .Select(p => new
    {
        Parent = p,
        Children = p.Children.Where(childPredicateStarter)
    })
    .AsEnumerable()
    .Select(p => p.Parent);

但这不会运行,因为p.ChildrenIEnumerable,所以childPredicateStarter 隐式转换为Func 而不是必需的Expression&lt;Func&gt;&gt;。请参阅here 以获得深入的解释。

实际工作版本是:

// Convert to expression:
Expression<Func<Child, bool>> childPredicate = childPredicateStarter;

var p = db.Parents.AsExpandable() // <-- LINQKit's AsExpandable()
    .Where(p => parentNames.Contains(p.Name))
    .Select(p => new
    {
        Parent = p,
        Children = p.Children.Where(c => childPredicate.Invoke(c))
    })
    .AsEnumerable()
    .Select(p => p.Parent);

AsExpandable 调用将Invoke 转换回适当的表达式树,EF 可以将其转换为 SQL。

【讨论】:

    【解决方案2】:

    Parent.Childs 包含父级的所有子级,而不仅仅是想要的子级

    过滤的包含即将推出,但尚未实施。鉴于您的代码明确声明包含所有儿童,我对您为什么认为它实际上会过滤感到有点茫然...

    .Include(p => p.Childs)

    表示包括孩子(顺便说一句,childs 是糟糕的英语 - 复数是 Children)。那里没有过滤器。

    关于过滤的包括:

    Filtering on Include in EF Core

    在这里引用相关部分:

    “最后,此功能已从 EF Core 预览版 5.0.0-preview.3.20181.2 开始实施,并将在 EF Core 5.0.0 版中为 GA”

    但即便如此,您也必须进行过滤(即,将 where 放入包含中,而不仅仅是告诉它获取所有内容)。

    【讨论】:

    • 孩子们,感谢指正。假设我使用的是 EF Core 5,如果我错了,请告诉我,但我需要 Include 上的 WhereDbSet 上的 _db.Parents.Include(p =&gt; p.Childs.Where(/* parent1 &amp;&amp; child 1 || parent2 &amp;&amp; child2 || ... */)).Where(/* parent1 || parent2 || ... */) 类似 _db.Parents.Include(p =&gt; p.Childs.Where(/* parent1 &amp;&amp; child 1 || parent2 &amp;&amp; child2 || ... */)).Where(/* parent1 || parent2 || ... */)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-27
    • 1970-01-01
    • 1970-01-01
    • 2010-12-11
    • 2021-02-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多