使用多个OR进行快速查询,并使用Entity Framework Core对联接的表进行过滤 [英] Express query with multiple OR and filter on joined table with Entity Framework Core

查看:48
本文介绍了使用多个OR进行快速查询,并使用Entity Framework Core对联接的表进行过滤的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我的模特

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查询如下所示:

With a given list of couples (parent name, child name) I'd like to get the couples (parent, child) where child can be null if the parent with the given name exists but not the child. The SQL query would look like this:

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

我尝试使用 PredicateBuilder 来使用Entity Framework Core表示此查询.或与False方法

I've tried expressing this query with Entity Framework Core using PredicateBuilder for the Or and False methods

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

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

This is the closest I could get but this doesn't get the expected the result:

  • 如果子代不存在,则父代不出现在结果集中
  • Parent.Childs 包含父级的所有子级,而不只是所需的子级
  • if the child doesn't exist the parent is not present in the result set
  • Parent.Childs contains all children of the parent instead of only the wanted one

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

Is my query expressible with Entity Framework Core?

推荐答案

根据您的评论,现在的要求是:给我所有按名称指定的父母,并且如果有的话,每个父母只有一个特定的孩子.也就是说:结果中将显示有 other 个孩子的父母,但没有孩子.

As per your comment, the requirement now is: give me all parents, specified by name, and only one specific child per parent, if present. That is: parents having other children will appear in the result, but without children.

这听起来很琐碎,但事实并非如此.需要注意的是,它需要两个过滤器,一个在父级上,一个在子级上,其中子级过滤器甚至是特定于父级的.SQL查询如下所示:

That sounds rather trivial, but it isn't. The gotcha is that it requires two filters, one on parents and one on children, in which the child filter is even parent-specific. A SQL query would look like this:

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 .

For an EF LINQ query the parent predicate can be a simple Contains, but you'd want to build the predicate using a predicate builder. Here, for reason following later, I use LINQkit.core.

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

To be able to build the predicates from one source I use a temporary structure (but I guess you already have something similar):

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 中缺少过滤的问题:

Now, ideally, the LINQ query would look like this (db is a context), working around the lack of filtering in 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.Children IEnumerable ,所以 childPredicateStarter 隐式转换为 Func 而不是必需的 Expression< Func>> .有关详细说明,请参见此处.

But that doesn't run because p.Children is IEnumerable, so childPredicateStarter implicitly converts to a Func instead of the required Expression<Func>>. See here for an in-depth explanation.

实际的工作版本是:

// 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.

The AsExpandable call converts the Invoke back into a proper expression tree that EF can translate into SQL.

这篇关于使用多个OR进行快速查询,并使用Entity Framework Core对联接的表进行过滤的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆