我想对一组数据应用多个复合过滤器,如下所示:
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中可能)
答案 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();
像魅力一样工作。谢谢。