如何在EF Core中查询谓词组

时间:2019-05-13 15:04:15

标签: c# entity-framework-core

我想对一组数据应用多个复合过滤器,如下所示:

Filter[] filters = new[] { new Filter { Name = "Bob", Gender = "Male"  },
new Filter { Name = "Alice", Height = "Female" } };

_dbContext.People.Where(p => filter.Any(f => f.Name == p.Name && f.Gender == p.Gender)).Select(p => p.Id);

我对男鲍勃女爱丽丝Ids感兴趣。抱歉,女鲍勃。我不要你。

这是解决内存Linq中的问题的正确方法,但是 有问题这就是SQL EF生成的样子(我正在SQL Server Profiler中检查它)

SELECT [p].[Name], [p].[Gender], [p].[Id] FROM [People] AS [p]

这太可怕了。它会挖掘所有内容,然后在内存中进行实际工作。这不可能与很多人一起使用,它会停顿下来。

有什么方法可以使生成的sql看起来更像这样吗?

SELECT 
[Person].[Id]
FROM [Person] 
WHERE 
   ((([Person].[Name] = "Bob") AND ([Person].[Gender] = "Male")) 
   OR (([Person].[Name] = "Alice") AND ([Person].[Gender] = "Female")))

(在Dapper中可能)

3 个答案:

答案 0 :(得分:2)

查询的结构表明姓名/性别组合的数量可能超过两个。在这种情况下,编写自己的存储过程而不是让EF创建查询可能更有意义。

在这种情况下,我将使用table-valued parameter将一组行作为参数传递给存储过程。每行都包含一个名称和一个性别。该查询将该表参数连接到您的Person表,并返回名称和性别都匹配的行。

答案 1 :(得分:1)

在这种特殊情况下(如果您只有少量过滤器),我建议将查询拆分为显式表达式,例如

var maleNameFilter = "Bob";
var femaleNameFilter = "Alice";
_dbContext.People.Where(p => 
    (p.Name == maleNameFilter && p.Gender == "Male") 
    || (p.Name == femaleNameFilter && p.Gender == "Female")
).Select(p => p.Id);

但是我怀疑您会想使用大量的过滤器,在这种情况下,使用LINQ会变得更加困难。正如某些注释中已经建议的那样,您可以为此使用Predicate Builder(请参阅example)。

最后,如果您想以更高的复杂度为代价获得最佳性能,则可以考虑将过滤器值放入数据库中的单独表中,然后使用Join()重写查询。

答案 2 :(得分:1)

这就是我最后做的事,如@stuartd建议的那样:

var predicate = PredicateBuilder.New<Person>();
foreach (var filter in filters)
{
    predicate = predicate.Or(p =>
       p.Gender == filter.Gender &&  filter.Name == p.Name));
}

people = _dbContext.People.Where(predicate).Select(r => r.Id).Distinct().ToArrayAsync();

像魅力一样工作。谢谢。