这是我的模型
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');
我尝试用实体框架核心来表达这个查询,对于Or和False方法我使用了PredicateBuilder
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
包含父级的所有子级,而不是仅包含所需的子级。我的查询可以用实体框架核心来表示吗?
发布于 2020-06-18 21:11:04
根据您的意见,现在的要求是:给我所有的父母,指定的名字,并只有一个特定的孩子每个家长,如果有。也就是说:有其他孩子的父母会出现在结果中,但没有孩子。
这听起来很琐碎,但事实并非如此。问题是,它需要两个过滤器,一个是父母的过滤器,一个是孩子的过滤器,在这个过滤器中,孩子的过滤器甚至是父母特有的。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查询,父谓词可以是一个简单的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.Children
是IEnumerable
,所以childPredicateStarter
不运行,因此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。
发布于 2020-06-14 12:27:26
Parent.Childs包含父级的所有子类,而不是仅包含所需的
过滤包含即将到来,但尚未实现。我有点不知所措,为什么你认为它真的会过滤,因为你的代码明确规定要使所有的孩子.
.Include(p => p.Childs)
手段包括孩子(顺便说一句,孩子英语不好-复数是儿童)。那里没有过滤器。
关于过滤,包括:
在此引用有关部分:
“最后,此功能已从EF Core预览版本5.0.0-preview.3.20181.2开始实施,并将成为EF核心版本5.0.0中的GA。”
但即便如此,您也必须进行过滤(即将ptut过滤到包含中,而不仅仅是告诉它获取所有这些内容)。
https://stackoverflow.com/questions/62372255
复制相似问题