簡體   English   中英

使用多個 OR 快速查詢並使用 Entity Framework Core 對連接表進行過濾

[英]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.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。

Parent.Childs 包含父母的所有孩子,而不僅僅是想要的孩子

過濾的包含即將到來,但尚未實施。 鑒於您的代碼明確聲明包含所有兒童,我對您為什么認為它實際上會過濾感到有點茫然...

.Include(p => p.Childs)

手段包括孩子(順便說一句,孩子是壞英語 - 復數是孩子)。 那里沒有過濾器。

關於過濾包括:

過濾包含在 EF Core 中

在這里引用相關部分:

“最后,此功能已從 EF Core 預覽版 5.0.0-preview.3.20181.2 開始實施,並將在 EF Core 5.0.0 版中成為 GA”

但即便如此,您也必須進行過濾(即,將其中的內容放入包含中,而不僅僅是告訴它獲取所有內容)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM