我正在创建一个搜索数据库的应用程序,并允许用户动态添加任何条件(大约 50 个可能),类似于以下 SO 问题:使用创建动态查询
我正在创建一个搜索数据库的应用程序,并允许用户动态添加任何条件(大约 50 个),就像以下 SO 问题一样: 使用实体框架创建动态查询 。我目前正在进行一个检查每个条件的搜索,如果它不为空,它会将其添加到查询中。
C#
var query = Db.Names.AsQueryable();
if (!string.IsNullOrWhiteSpace(first))
query = query.Where(q => q.first.Contains(first));
if (!string.IsNullOrWhiteSpace(last))
query = query.Where(q => q.last.Contains(last));
//.. around 50 additional criteria
return query.ToList();
此代码在 SQL Server 中产生类似于以下内容的内容(为了便于理解,我进行了简化)
SQL
SELECT
[Id],
[FirstName],
[LastName],
...etc
FROM [dbo].[Names]
WHERE [FirstName] LIKE '%first%'
AND [LastName] LIKE '%last%'
我现在正在尝试添加一种方法,通过实体框架用 C# 生成以下 SQL,但使用 OR 而不是 AND ,同时仍然保留动态添加条件的能力。
SQL
SELECT
[Id],
[FirstName],
[LastName],
...etc
FROM [dbo].[Names]
WHERE [FirstName] LIKE '%first%'
OR [LastName] LIKE '%last%' <-- NOTICE THE "OR"
通常,查询的条件不会超过两三个项目,但将它们组合成一个巨大的查询不是一个选择。我尝试过 concat、union 和 intersect,它们都只是复制查询并使用 UNION 将它们连接起来。
是否有一种简单干净的方法可以使用实体框架向动态生成的查询添加“OR”条件?
使用我的解决方案进行编辑 - 2015 年 9 月 29 日
自从发布这篇文章以来,我注意到这个问题引起了一些关注,所以我决定发布我的解决方案
// Make sure to add required nuget
// PM> Install-Package LinqKit
var searchCriteria = new
{
FirstName = "sha",
LastName = "hill",
Address = string.Empty,
Dob = (DateTime?)new DateTime(1970, 1, 1),
MaritalStatus = "S",
HireDate = (DateTime?)null,
LoginId = string.Empty,
};
var predicate = PredicateBuilder.False<Person>();
if (!string.IsNullOrWhiteSpace(searchCriteria.FirstName))
{
predicate = predicate.Or(p => p.FirstName.Contains(searchCriteria.FirstName));
}
if (!string.IsNullOrWhiteSpace(searchCriteria.LastName))
{
predicate = predicate.Or(p => p.LastName.Contains(searchCriteria.LastName));
}
// Quite a few more conditions...
foreach(var person in this.Persons.Where(predicate.Compile()))
{
Console.WriteLine("First: {0} Last: {1}", person.FirstName, person.LastName);
}
是否有一种简单干净的方法可以使用实体框架向动态生成的查询添加“OR”条件?
是的,您可以通过简单地依赖 where
包含单个布尔表达式的单个子句来实现这一点,该表达式的 OR
各个部分在运行时动态地被“禁用”或“启用”,从而避免必须安装 LINQKit 或编写自定义谓词构建器。
参考你的例子:
var isFirstValid = !string.IsNullOrWhiteSpace(first);
var isLastValid = !string.IsNullOrWhiteSpace(last);
var query = db.Names
.AsQueryable()
.Where(name =>
(isFirstValid && name.first.Contains(first)) ||
(isLastValid && name.last.Contains(last))
)
.ToList();
根据先前评估的前提(例如 where
动态地打开或关闭过滤表达式的 OR 部分 isFirstValid
。
例如,如果 短路FirstValid
不是 true
,则被 name.first.Contains(first)
is , 既不会被执行,也不会影响结果集。此外,EF Core DefaultQuerySqlGenerator
在执行之前 进一步 优化和减少 where
其中的布尔表达式 false && x || true && y || false && z
可以 y
通过简单的静态分析将其简化为简单)。
请注意:如果所有前提都不为 true
,则结果集将为空 — 我认为这是您的情况所需的行为。但是,如果您出于某种原因更喜欢从源中选择所有元素 IQueryable
,则可以将最终变量添加到表达式中以求值 true
(例如, .Where( ... || shouldReturnAll)
使用 var shouldReturnAll = !(isFirstValid || isLastValid)
或类似的东西)。
最后一点:这种技术的缺点是,它迫使您构建一个“集中式”布尔表达式,该表达式位于查询所在的同一方法主体中(更准确地说是 where
查询的一部分)。如果您出于某种原因想要分散谓词的构建过程并将它们作为参数注入或通过查询构建器链接它们,那么您最好坚持使用谓词构建器,如其他答案中所建议的那样。否则,请享受这种简单的技术 :)