繁体   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