C# Linq:将多个 .Where() 与 *OR* 子句组合
C# Linq: Combine multiple .Where() with an *OR* clause
我一直在搜索有关我当前问题的大量信息,但找不到解决该问题的真正答案。
我正在尝试构建一个生成以下内容的 LINQ 查询 SQL:
SELECT * FROM TABLE WHERE (Field1 = X, Field2 = Y ... ) or (Field3 = Z)
在正常情况下我会这样做:
Object.Where(c => (c.Field1 == X && c.Field2 == Y) || (c.Field3 == Z))
我不能使用这种方法,因为查询是通过使用多个 .Where()
调用构建的。
举个例子:
// This is a short example, the real world situation has 20 fields to check and they are all connected with an AND.
if (model.Field1.HasValue)
{
Query = Query.Where(c => c.Field1 == X)
}
if (model.Field2.HasValue)
{
Query = Query.Where(c => c.Field2 == X)
}
[...] like 20 more of these .Where() calls.
这就是它对我来说变得复杂的原因。所有这些 .Where()
调用都在构建与 AND
连接的 Linq 查询,这很好。
如何让它们使用括号执行并添加一个简单的 OR
现在使用 API?
有没有办法将谓词保存在一些变量中,这样我就可以做类似的事情:
Query = Query.Where(c => previousPredicates || c.Field3 == X)
或者如何解决这个问题?
我认为这个特定问题一定有一个好的解决方案,我不是唯一需要它的人,但我完全不确定如何实现它。
P.S:我真的不能删除多个 .Where()
调用,直接写 SQL 也不是一个选项。
编辑
Whosebug 要我说明为什么我的问题与其他问题不同。好吧,事情是关于 Parentheses
。我不想将所有 .Where()
与单个 OR 子句连接起来,我想将它们与 AND
一起保留并添加另一个 OR
子句,同时所有 AND
查询都被括起来.
首先,创建一些辅助扩展方法以更轻松地组合两个 Func<T,bool>
谓词:
public static Func<T, bool> And<T>(this Func<T, bool> left, Func<T, bool> right)
=> a => left(a) && right(a);
public static Func<T, bool> Or<T>(this Func<T, bool> left, Func<T, bool> right)
=> a => left(a) || right(a);
然后您可以使用它们来链接谓词:
var list = Enumerable.Range(1, 100);
Func<int, bool> predicate = v => true; // start with true since we chain ANDs first
predicate = predicate.And(v => v % 2 == 0); // numbers dividable by 2
predicate = predicate.And(v => v % 3 == 0); // numbers dividable by 3
predicate = predicate.Or(v => v % 31 == 0); // numbers dividable by 31
var result = list.Where(predicate);
foreach (var i in result)
Console.WriteLine(i);
输出:
6
12
18
24
30
31
36
42
48
54
60
62
66
72
78
84
90
93
96
您可以使用 Expression
一步创建,如下所示:
Expression<Func<Model, bool>> exp = (model =>
((model.Field1.HasValue && c.Field1 == X) &&
(model.Field2.HasValue && c.Field2 == X)) ||
model.Field3 == X
)
一旦定义了谓词,就可以很容易地在查询中使用它们。
var result = Query.AsQueryable().Where(exp)
检查这个要点中的代码:
my gist url
更新 1:
如果你必须使用步骤来创建你的表达式,你可以使用这个:
Expression<Func<Model, bool>> exp = c => true;
if (model.Field1.HasValue)
{
var prefix = exp.Compile();
exp = c => prefix(c) && c.Field1 == X;
}
if (model.Field2.HasValue)
{
var prefix = exp.Compile();
exp = c => prefix(c) && c.Field2 == X;
}
[...] like 20 more of these .Where() calls.
如果您想以编程方式构建查询并让它在您的 SQL 服务器上执行,而不是获取所有记录并在内存中查询,您需要在 Expression
class 并使用它们构建您的查询。在您的示例中:
public class Query // this will contain your 20 fields you want to check against
{
public int? Field1; public int? Field2; public int? Field3; public int Field4;
}
public class QueriedObject // this is the object representing the database table you're querying
{
public int QueriedField;
}
public class Program
{
public static void Main()
{
var queryable = new List<QueriedObject>().AsQueryable();
var query = new Query { Field2 = 1, Field3 = 4, Field4 = 2 };
// this represents the argument to your lambda expression
var parameter = Expression.Parameter(typeof(QueriedObject), "qo");
// this is the "qo.QueriedField" part of the resulting expression - we'll use it several times later
var memberAccess = Expression.Field(parameter, "QueriedField");
// start with a 1 == 1 comparison for easier building -
// you can just add further &&s to it without checking if it's the first in the chain
var expr = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
// doesn't trigger, so you still have 1 == 1
if (query.Field1.HasValue)
{
expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field1.Value)));
}
// 1 == 1 && qo.QueriedField == 1
if (query.Field2.HasValue)
{
expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field2.Value)));
}
// 1 == 1 && qo.QueriedField == 1 && qo.QueriedField == 4
if (query.Field3.HasValue)
{
expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field3.Value)));
}
// (1 == 1 && qo.QueriedField == 1 && qo.QueriedField == 4) || qo.QueriedField == 2
expr = Expression.OrElse(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field4)));
// now, we combine the lambda body with the parameter to create a lambda expression, which can be cast to Expression<Func<X, bool>>
var lambda = (Expression<Func<QueriedObject, bool>>) Expression.Lambda(expr, parameter);
// you can now do this, and the Where will be translated to an SQL query just as if you've written the expression manually
var result = queryable.Where(lambda);
}
}
好的,关于 linq,您已经有了自己的答案。
让我介绍一种使用 Dynamic.linq
的不同方法
// You could build a Where string that can be converted to linq.
// and do if sats and append your where sats string. as the example below
var query = "c => (c.Field1 == \" a \" && c.Field2 == Y) || (c.Field3 == \" b \")";
var indicator = query.Split('.').First(); // the indicator eg c
// assume TABLE is the name of the class
var p = Expression.Parameter(typeof(TABLE), indicator);
var e = DynamicExpression.ParseLambda(new[] { p }, null, query);
// and simple execute the expression
var items = Object.Where(e);
我一直在搜索有关我当前问题的大量信息,但找不到解决该问题的真正答案。
我正在尝试构建一个生成以下内容的 LINQ 查询 SQL:
SELECT * FROM TABLE WHERE (Field1 = X, Field2 = Y ... ) or (Field3 = Z)
在正常情况下我会这样做:
Object.Where(c => (c.Field1 == X && c.Field2 == Y) || (c.Field3 == Z))
我不能使用这种方法,因为查询是通过使用多个 .Where()
调用构建的。
举个例子:
// This is a short example, the real world situation has 20 fields to check and they are all connected with an AND.
if (model.Field1.HasValue)
{
Query = Query.Where(c => c.Field1 == X)
}
if (model.Field2.HasValue)
{
Query = Query.Where(c => c.Field2 == X)
}
[...] like 20 more of these .Where() calls.
这就是它对我来说变得复杂的原因。所有这些 .Where()
调用都在构建与 AND
连接的 Linq 查询,这很好。
如何让它们使用括号执行并添加一个简单的 OR
现在使用 API?
有没有办法将谓词保存在一些变量中,这样我就可以做类似的事情:
Query = Query.Where(c => previousPredicates || c.Field3 == X)
或者如何解决这个问题?
我认为这个特定问题一定有一个好的解决方案,我不是唯一需要它的人,但我完全不确定如何实现它。
P.S:我真的不能删除多个 .Where()
调用,直接写 SQL 也不是一个选项。
编辑
Whosebug 要我说明为什么我的问题与其他问题不同。好吧,事情是关于 Parentheses
。我不想将所有 .Where()
与单个 OR 子句连接起来,我想将它们与 AND
一起保留并添加另一个 OR
子句,同时所有 AND
查询都被括起来.
首先,创建一些辅助扩展方法以更轻松地组合两个 Func<T,bool>
谓词:
public static Func<T, bool> And<T>(this Func<T, bool> left, Func<T, bool> right)
=> a => left(a) && right(a);
public static Func<T, bool> Or<T>(this Func<T, bool> left, Func<T, bool> right)
=> a => left(a) || right(a);
然后您可以使用它们来链接谓词:
var list = Enumerable.Range(1, 100);
Func<int, bool> predicate = v => true; // start with true since we chain ANDs first
predicate = predicate.And(v => v % 2 == 0); // numbers dividable by 2
predicate = predicate.And(v => v % 3 == 0); // numbers dividable by 3
predicate = predicate.Or(v => v % 31 == 0); // numbers dividable by 31
var result = list.Where(predicate);
foreach (var i in result)
Console.WriteLine(i);
输出:
6
12
18
24
30
31
36
42
48
54
60
62
66
72
78
84
90
93
96
您可以使用 Expression
一步创建,如下所示:
Expression<Func<Model, bool>> exp = (model =>
((model.Field1.HasValue && c.Field1 == X) &&
(model.Field2.HasValue && c.Field2 == X)) ||
model.Field3 == X
)
一旦定义了谓词,就可以很容易地在查询中使用它们。
var result = Query.AsQueryable().Where(exp)
检查这个要点中的代码: my gist url
更新 1: 如果你必须使用步骤来创建你的表达式,你可以使用这个:
Expression<Func<Model, bool>> exp = c => true;
if (model.Field1.HasValue)
{
var prefix = exp.Compile();
exp = c => prefix(c) && c.Field1 == X;
}
if (model.Field2.HasValue)
{
var prefix = exp.Compile();
exp = c => prefix(c) && c.Field2 == X;
}
[...] like 20 more of these .Where() calls.
如果您想以编程方式构建查询并让它在您的 SQL 服务器上执行,而不是获取所有记录并在内存中查询,您需要在 Expression
class 并使用它们构建您的查询。在您的示例中:
public class Query // this will contain your 20 fields you want to check against
{
public int? Field1; public int? Field2; public int? Field3; public int Field4;
}
public class QueriedObject // this is the object representing the database table you're querying
{
public int QueriedField;
}
public class Program
{
public static void Main()
{
var queryable = new List<QueriedObject>().AsQueryable();
var query = new Query { Field2 = 1, Field3 = 4, Field4 = 2 };
// this represents the argument to your lambda expression
var parameter = Expression.Parameter(typeof(QueriedObject), "qo");
// this is the "qo.QueriedField" part of the resulting expression - we'll use it several times later
var memberAccess = Expression.Field(parameter, "QueriedField");
// start with a 1 == 1 comparison for easier building -
// you can just add further &&s to it without checking if it's the first in the chain
var expr = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
// doesn't trigger, so you still have 1 == 1
if (query.Field1.HasValue)
{
expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field1.Value)));
}
// 1 == 1 && qo.QueriedField == 1
if (query.Field2.HasValue)
{
expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field2.Value)));
}
// 1 == 1 && qo.QueriedField == 1 && qo.QueriedField == 4
if (query.Field3.HasValue)
{
expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field3.Value)));
}
// (1 == 1 && qo.QueriedField == 1 && qo.QueriedField == 4) || qo.QueriedField == 2
expr = Expression.OrElse(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field4)));
// now, we combine the lambda body with the parameter to create a lambda expression, which can be cast to Expression<Func<X, bool>>
var lambda = (Expression<Func<QueriedObject, bool>>) Expression.Lambda(expr, parameter);
// you can now do this, and the Where will be translated to an SQL query just as if you've written the expression manually
var result = queryable.Where(lambda);
}
}
好的,关于 linq,您已经有了自己的答案。
让我介绍一种使用 Dynamic.linq
的不同方法// You could build a Where string that can be converted to linq.
// and do if sats and append your where sats string. as the example below
var query = "c => (c.Field1 == \" a \" && c.Field2 == Y) || (c.Field3 == \" b \")";
var indicator = query.Split('.').First(); // the indicator eg c
// assume TABLE is the name of the class
var p = Expression.Parameter(typeof(TABLE), indicator);
var e = DynamicExpression.ParseLambda(new[] { p }, null, query);
// and simple execute the expression
var items = Object.Where(e);