我的主系统实体被“标记”为键值对的子集合,我想用它来过滤主要实体的列表。但是,我在下面编写的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查询时似乎已经不得不诉诸于此了!
答案 0 :(得分:2)
首先要注意的是,您提出的查询无法完全作为SQL进行评估,因为对于包含非原始值tagSearchValues
的集合没有SQL等效项。这导致EF自动切换到client-side evaluation。也就是说,它将所有符合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一样)不适用于服务器端的这种成对比较。因此,您需要谓词生成器来构建标记谓词。有几个谓词buiders,例如在Linqkit中。我使用this one是因为它很好而且很简单。诀窍是:建立谓词并将其应用于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
,是因为(从您的查询中)我假设您想要在搜索标签中具有 any 标签的实体。这就是为什么我以PredicateBuilder.True
谓词开头的原因,因此如果没有搜索标签,该查询将返回结果,类似于您的原始查询。
答案 1 :(得分:0)
您知道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)))