簡體   English   中英

EF Core-通過鍵值對的子級集合進行篩選非常慢

[英]EF Core - Filtering by a child collection of Key Value pairs extremely slow

我的主系統實體被“標記”為鍵值對的子集合,我想用它來過濾主要實體的列表。 但是,我在下面編寫的EF核心查詢的速度太慢,無法接受。

簡化實體類

 public class MainEntity
 {
    public int Id { get; set; }
    public DateTimeOffset Created { get; set; }
    public string Stuff {get; set;}
    public virtual List<Tag> Tags { get; set; }
 }

 public class Tag
 {
    public int Id { get; set; }
    public string Key { get; set; }
    public string Value { get; set; }

    public int MainEntityId { get; set; }
    public virtual MainEntity MainEntity { get; set; }
 }

簡化查詢

//filter params passed into the query function
//String? stuffFilter
//List<Tag> tagSearchValues

var query = _dbContext.MainEntities.Where(
    me => ((!stuffFilter.HasValue || me.Stuff == stuffFilter.Value)                    
    && (tagSearchValues == null || tagSearchValues.Count == 0 ||
    (
    (me.Tags.Select(t => t.Key).Any(tk => tagSearchValues.Select(s => s.Key).Any(sk => sk == tk))) &&
    (me.Tags.Select(t => t.Value).Any(tv => tagSearchValues.Select(s => s.Value).Any(sv => sv == tv)))
    )
    ).                    
    OrderByDescending(l => me.Created).AsNoTracking();

我對EF(第一次使用EF Core)有點生疏,但是問題出在我用多個.Any()命令按子Tag集合過濾的方式(查詢在未指定標簽過濾器)。

我想不出另一種針對所選Tag過濾器對象過濾子Tag對象集合的方法-我想,單個過濾器Tag會更簡單,更快捷。

我目前唯一想到的選擇是自己做一個自定義SQL查詢,但是在組合我的第一個EF Core查詢時似乎已經不得不訴諸於此了!

首先要注意的是,您提出的查詢無法完全作為SQL進行評估,因為對於包含非原始值tagSearchValues的集合,沒有等效的SQL。 這導致EF自動切換到客戶端評估 也就是說,它將所有符合stuffFilter條件的實體及其所有標簽拉入內存,然后應用標簽謂詞。 顯然,這是無效的。

其次,查詢不准確。 包含與特定鍵,並與特定的值標簽的標簽的實體是不一樣的含有特定的鍵/值組合的標簽。 它需要一個與每個組合匹配的查詢,如下所示:

db.MainEntities.Where(...)
    .Where(m => tagSearchValues
       .Any(t => m.Tags.Any(mt => mt.Key == t.Key 
                               && mt.Value == t.Value)))

但是,如果這樣做,EF將再次轉向效率低下的客戶端評估,您甚至必須應用Include或惰性加載自己才能將標簽拉入內存。 (此外,由於某種原因,EF會觸發大量冗余查詢)。

事實是,EF(與其他ORM一樣)不適用於服務器端的這種成對比較。 因此,您需要謂詞生成器來構建標記謂詞。 有幾個謂語助洗劑,鐵在Linqkit 我使用這個是因為它既好又簡單。 訣竅是:建立一個謂詞並將其應用於Where()

var tagPredicate = PredicateBuilder.True<MainEntity>();
if (tagSearchValues.Any())
{
    tagPredicate = PredicateBuilder.False<MainEntity>();
    foreach (var tag in tagSearchValues)
    {
        tagPredicate = tagPredicate.Or(m => m.Tags
                           .Any(t => t.Key == tag.Key
                                  && t.Value == tag.Value));
    }
}

var query = _dbContext.MainEntities
    .Where(m => string.IsNullOrWhiteSpace(stuff) || m.Stuff == stuff)
    .Where(tagPredicate);
... // Use query

我使用Or因為我假設(根據您的查詢)您希望實體在搜索標簽中具有任何標簽。 這就是為什么我從PredicateBuilder.True謂詞開始,因此如果沒有搜索標簽,查詢將返回結果,類似於您的原始查詢。

您知道EF Core Any生成什么SQL嗎? EF Core具有不幸的設計屬性,如果無法將查詢轉換為SQL,則會在客戶端以靜默方式執行查詢。

如果您整合了KeyValue測試該怎么辦?

(me.Tags.Any(met => tagSearchValues.Any(st => st.Tag == met.Tag && st.Value == met.Value)))

或者,如果您改用Contains怎么辦?

(me.Tags.Select(t => t.Key).Any(tk => tagSearchValues.Select(s => s.Key).Contains(tk))) &&
(me.Tags.Select(t => t.Value).Any(tv => tagSearchValues.Select(s => s.Value).Contains(tv)))

暫無
暫無

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

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