[英]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,则会在客户端以静默方式执行查询。
如果您整合了Key
和Value
测试该怎么办?
(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.