简体   繁体   English

EF Core-通过键值对的子级集合进行筛选非常慢

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

My main system entity is 'tagged' with a child collection of key value pairs, which I want to use to filter a listing of the main entities. 我的主系统实体被“标记”为键值对的子集合,我想用它来过滤主要实体的列表。 However, the EF core query I've written below is far too slow for acceptable use. 但是,我在下面编写的EF核心查询的速度太慢,无法接受。

Simplified Entity Classes 简化实体类

 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; }
 }

Simplified Query 简化查询

//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();

I'm a bit rusty with EF (and using EF Core for the first time), but the problem is down to the way I'm filtering by the child Tag collection with the multiple .Any() commands (the query performs perfectly when no Tag filters are specified). 我对EF(第一次使用EF Core)有点生疏,但是问题出在我用多个.Any()命令按子Tag集合过滤的方式(查询在未指定标签过滤器)。

I can't think of a another way to filter the child Tag object collection against the selected Tag filter objects - a single filter Tag would be much simpler, and quicker, I imagine. 我想不出另一种针对所选Tag过滤器对象过滤子Tag对象集合的方法-我想,单个过滤器Tag会更简单,更快捷。

The only alternative I can currently think of is to do a custom SQL query myself, but it seems a shame to resort to that already when putting together my first EF Core query! 我目前唯一想到的选择是自己做一个自定义SQL查询,但是在组合我的第一个EF Core查询时似乎已经不得不诉诸于此了!

The first thing to note is that your proposed query can't be evaluated fully as SQL because there's no SQL equivalent for a collection containing non-primitive values, tagSearchValues . 首先要注意的是,您提出的查询无法完全作为SQL进行评估,因为对于包含非原始值tagSearchValues的集合,没有等效的SQL。 This causes EF to auto-switch to client-side evaluation . 这导致EF自动切换到客户端评估 That is, it pulls into memory all entities that meet the stuffFilter condition and all of their tags, and then it applies the tags predicate. 也就是说,它将所有符合stuffFilter条件的实体及其所有标签拉入内存,然后应用标签谓词。 That, evidently, is not efficient. 显然,这是无效的。

Secondly, the query is inaccurate. 其次,查询不准确。 Entities containing tags with certain keys and tags with certain values isn't the same as tags containing specific key/value combinations . 包含与特定键,并与特定的值标签的标签的实体是不一样的含有特定的键/值组合的标签。 It requires a query that matches each combination, like this: 它需要一个与每个组合匹配的查询,如下所示:

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

However, if you do that, EF will again turn to inefficient client-side evaluation and you'd even have to apply Include or lazy loading yourself to pull the tags into memory. 但是,如果这样做,EF将再次转向效率低下的客户端评估,您甚至必须应用Include或惰性加载自己才能将标签拉入内存。 (Moreover, for some reason, EF fires tons of redundant queries). (此外,由于某种原因,EF会触发大量冗余查询)。

Fact of the matter is that EF (like other ORMs) isn't well-geared to such pair-wise comparisons server-side. 事实是,EF(与其他ORM一样)不适用于服务器端的这种成对比较。 Therefore you need a predicate builder to build the tag predicates. 因此,您需要谓词生成器来构建标记谓词。 There are several predicate buiders, fe in Linqkit . 有几个谓语助洗剂,铁在Linqkit I use this one because it's nice and simple. 我使用这个是因为它既好又简单。 The recipe is: build a predicate and apply it in a Where() : 诀窍是:建立一个谓词并将其应用于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

I use Or because I assume (from your query) that you want entities having any tag in the search tags. 我使用Or因为我假设(根据您的查询)您希望实体在搜索标签中具有任何标签。 That's why I start with a PredicateBuilder.True predicate, so the query will return results if there are no search tags, similar to your original query. 这就是为什么我从PredicateBuilder.True谓词开始,因此如果没有搜索标签,查询将返回结果,类似于您的原始查询。

Do you know what SQL is being generated by EF Core Any ? 您知道EF Core Any生成什么SQL吗? EF Core has the unfortunate design property of silently executing queries on the client side if they can't be translated to SQL. EF Core具有不幸的设计属性,如果无法将查询转换为SQL,则会在客户端以静默方式执行查询。

What if you consolidate the Key and Value testing? 如果您整合了KeyValue测试该怎么办?

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

Or what if you use Contains instead? 或者,如果您改用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