[英]Express query with multiple OR and filter on joined table with Entity Framework Core
這是我的 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
使用給定的夫婦列表(父母姓名,孩子姓名) ,如果具有給定姓名的父母存在但不存在孩子,我想獲得孩子可以是 null 的夫婦(父母,孩子) 。 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使用 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();
這是我能得到的最接近的結果,但這並沒有得到預期的結果:
Parent.Childs
包含父母的所有孩子,而不僅僅是想要的孩子我的查詢可以用 Entity Framework Core 表達嗎?
根據您的評論,現在的要求是:給我所有的父母,按姓名指定,如果有的話,每個父母只有一個特定的孩子。 即:有其他孩子的父母會出現在結果中,但沒有孩子。
這聽起來很微不足道,但事實並非如此。 問題是它需要兩個過濾器,一個在父母身上,一個在孩子身上,其中孩子過濾器甚至是特定於父母的。 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.Children
是IEnumerable
,因此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。
Parent.Childs 包含父母的所有孩子,而不僅僅是想要的孩子
過濾的包含即將到來,但尚未實施。 鑒於您的代碼明確聲明包含所有兒童,我對您為什么認為它實際上會過濾感到有點茫然...
.Include(p => p.Childs)
手段包括孩子(順便說一句,孩子是壞英語 - 復數是孩子)。 那里沒有過濾器。
關於過濾包括:
在這里引用相關部分:
“最后,此功能已從 EF Core 預覽版 5.0.0-preview.3.20181.2 開始實施,並將在 EF Core 5.0.0 版中成為 GA”
但即便如此,您也必須進行過濾(即,將其中的內容放入包含中,而不僅僅是告訴它獲取所有內容)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.