带多个or和filter的快速查询

oipij1gg  于 2021-08-09  发布在  Java
关注(0)|答案(2)|浏览(233)

这是我的模型

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

我尝试过用实体框架核心表示这个查询,使用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 包含父级的所有子级,而不是只包含所需的子级
我的查询可以用实体框架核心表达吗?

cbeh67ev

cbeh67ev1#

根据你的意见,现在的要求是:给我所有的父母,按姓名指定,每个父母只有一个特定的孩子,如果有的话。也就是说:有其他孩子的父母会出现在结果中,但没有孩子。
这听起来很琐碎,但事实并非如此。问题是它需要两个过滤器,一个在父过滤器上,一个在子过滤器上,其中子过滤器甚至是特定于父过滤器的。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查询,父 predicate 可以是一个简单的 Contains ,但您希望使用 predicate 生成器构建 predicate 。在这里,出于后面的原因,我使用linqkit.core。
为了能够从一个源构建 predicate ,我使用了一个临时结构(但我猜您已经有了类似的结构):

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

准备 predicate :

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<Func>> . 请参阅此处以获得深入的解释。
实际工作版本为:

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

wrrgggsh

wrrgggsh2#

parent.childs包含父级的所有子级,而不是只包含所需的子级
筛选包含即将到来,但尚未实现。我有点不明白为什么你认为它实际上会过滤,因为你的代码清楚地说明了所有的孩子。。。
.include(p=>p.childs)
意思是包括孩子(顺便说一句,childs是蹩脚英语-复数是childs)。那里没有过滤器。
有关过滤的信息包括:
过滤包含在ef核心中
在此引用相关部分:
“最后,此功能已从ef core preview版本5.0.0-preview.3.20181.2开始实施,并将在ef core版本5.0.0中正式推出。”
但即使这样,您也必须进行过滤(即ptut a where into the include,而不仅仅是告诉它获取所有这些内容)。

相关问题