简体   繁体   English

使用多个 OR 快速查询并使用 Entity Framework Core 对连接表进行过滤

[英]Express query with multiple OR and filter on joined table with Entity Framework Core

Here is my model这是我的 model

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

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.使用给定的夫妇列表(父母姓名,孩子姓名) ,如果具有给定姓名的父母存在但不存在孩子,我想获得孩子可以是 null 的夫妇(父母,孩子) The SQL query would look like this: 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');

I've tried expressing this query with Entity Framework Core using PredicateBuilder for the Or and False methods我已经尝试使用PredicateBuilder使用 Entity Framework Core 来表达这个查询,用于 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();

This is the closest I could get but this doesn't get the expected the result:这是我能得到的最接近的结果,但这并没有得到预期的结果:

  • 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 Parent.Childs包含父母的所有孩子,而不仅仅是想要的孩子

Is my query expressible with Entity Framework Core?我的查询可以用 Entity Framework Core 表达吗?

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.即:有其他孩子的父母会出现在结果中,但没有孩子。

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: 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

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.对于 EF LINQ 查询,父谓词可以是简单的Contains ,但您需要使用谓词构建器构建谓词。 Here, for reason following later, I use LINQkit.core .在这里,出于后面的原因,我使用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" },
};

And prepare the predicates:并准备谓词:

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

Now, ideally, the LINQ query would look like this ( db is a context), working around the lack of filtering in Include :现在,理想情况下,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);

But that doesn't run because p.Children is IEnumerable , so childPredicateStarter implicitly converts to a Func instead of the required Expression<Func>> .但这不会运行,因为p.ChildrenIEnumerable ,因此childPredicateStarter隐式转换为Func而不是所需的Expression<Func>> See here for an in-depth explanation.有关深入解释,请参见此处

The actual working version is:实际工作版本是:

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

The AsExpandable call converts the Invoke back into a proper expression tree that EF can translate into SQL. AsExpandable调用将Invoke转换回正确的表达式树,EF 可以将其转换为 SQL。

Parent.Childs contains all childs of the parent instead of only the wanted one Parent.Childs 包含父母的所有孩子,而不仅仅是想要的孩子

Filtered include is coming, but not implemented yet.过滤的包含即将到来,但尚未实施。 I am a little at a loss on why you think it would actually filter at all, given that your code CLEARLY states to incldue ALL children...鉴于您的代码明确声明包含所有儿童,我对您为什么认为它实际上会过滤感到有点茫然...

.Include(p => p.Childs) .Include(p => p.Childs)

Means include the children (btw., childs is bad english - the plural is Children).手段包括孩子(顺便说一句,孩子是坏英语 - 复数是孩子)。 There is no filter there.那里没有过滤器。

Regarding filtered include:关于过滤包括:

Filtering on Include in EF Core 过滤包含在 EF Core 中

Quoting the relevant part for here:在这里引用相关部分:

"Finally, this feature has been implemented starting with EF Core preview version 5.0.0-preview.3.20181.2 and will be GA in EF Core version 5.0.0" “最后,此功能已从 EF Core 预览版 5.0.0-preview.3.20181.2 开始实施,并将在 EF Core 5.0.0 版中成为 GA”

But even then, you will have to filter (ie ptut a where into the include, not just tell it to get all of them).但即便如此,您也必须进行过滤(即,将其中的内容放入包含中,而不仅仅是告诉它获取所有内容)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM