简体   繁体   English

C# Linq:将 multiple.Where() 与 *OR* 子句组合

[英]C# Linq: Combine multiple .Where() with an *OR* clause

I have been searching a lot about my current problem but I could not find a real answer to solve that issue.我一直在搜索有关我当前问题的大量信息,但找不到解决该问题的真正答案。

I am trying to build a LINQ Query that produces the following SQL:我正在尝试构建一个生成以下 SQL 的 LINQ 查询:

SELECT * FROM TABLE WHERE (Field1 = X, Field2 = Y ... ) or (Field3 = Z)

In a normal situation I would just do this:在正常情况下,我会这样做:

Object.Where(c => (c.Field1 == X && c.Field2 == Y) || (c.Field3 == Z))

I cannot use this approach because the query is build by using multiple .Where() calls.我不能使用这种方法,因为查询是通过使用多个.Where()调用构建的。

Having an example:举个例子:

// 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.

and that is how it gets complicated for me.这就是它对我来说变得复杂的原因。 All these .Where() calls are building a Linq Query that is connected with AND , which is fine.所有这些.Where()调用都在构建一个与AND连接的 Linq 查询,这很好。

How do I let them execute with Parenthese and add a simple OR now using the API?我如何让他们使用括号执行并添加一个简单的OR现在使用 API?

Is there a way to save the predicate in some variables so I can make something like:有没有办法将谓词保存在一些变量中,这样我就可以做类似的事情:

Query = Query.Where(c => previousPredicates || c.Field3 == X)

or how to solve that problem?或者如何解决这个问题?

I think there must be a good solution for that particual problem and I am not the only one who needs it, but I am absolute unsure how to achieve it.我认为这个特定问题必须有一个好的解决方案,我不是唯一需要它的人,但我绝对不确定如何实现它。

PS: I can't really remove the multiple .Where() calls and writing direct SQL is neither an option. PS:我无法真正删除多个.Where()调用,直接写入 SQL 也不是一种选择。

EDIT StackOverflow wants me to say why my question is different from others.编辑StackOverflow 要我说出为什么我的问题与其他问题不同。 Well, the thing is about Parentheses .好吧,事情是关于Parentheses的。 I do not want to connect all .Where() with a single OR clause, I want to leave them with AND and add another OR clause while all the AND queries are being parenthesied.我不想将所有.Where()与单个 OR 子句连接起来,我想将它们保留为AND并添加另一个OR子句,同时所有AND查询都被括起来。

If you want to build your query programmatically and have it execute on your SQL server instead of fetching all records and querying in memory, you need to use the set of static methods on the Expression class and build your query using those. 如果要以编程方式构建查询并使其在SQL服务器上执行而不是获取所有记录并在内存中查询,则需要在Expression类上使用静态方法集并使用这些方法构建查询。 In your example: 在你的例子中:

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);       
    }
}

First, create some helper extension methods to easier combine two Func<T,bool> predicates: 首先,创建一些辅助扩展方法,以便更容易地组合两个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);

Then you can use them to chain predicates: 然后你可以用它们来链接谓词:

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);

Output: 输出:

6
12
18
24
30
31
36
42
48
54
60
62
66
72
78
84
90
93
96

You can use Expression to create in one step like this: 您可以使用Expression在一个步骤中创建,如下所示:

Expression<Func<Model, bool>> exp = (model => 
                                    ((model.Field1.HasValue && c.Field1 == X) &&
                                    (model.Field2.HasValue && c.Field2 == X)) ||
                                     model.Field3 == X
                                    )

Once you have your predicates defined, it's very easy to use them in a query. 一旦定义了谓词,就可以很容易地在查询中使用它们。

var result = Query.AsQueryable().Where(exp)

Check the code in this gist: my gist url 检查这个要点中的代码: 我的要点网址

UPDATE 1: If You have to use steps to create your expression you can use this: 更新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.

Ok you have had your own share of answer about linq. 好的,你有自己的关于linq的回答。

Let me introduce a different approach using Dynamic.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);

i search and found useful extension method:我搜索并找到了有用的扩展方法:

public static class ExpressionHelper
{
    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) where T : class
    {
        return CreateExpression(left, right, ExpressionType.And);
    }

    public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) where T : class
    {
        return CreateExpression(left, right, ExpressionType.AndAlso);
    }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) where T : class
    {
        return CreateExpression(left, right, ExpressionType.Or);
    }

    public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) where T : class
    {
        return CreateExpression(left, right, ExpressionType.OrElse);
    }

    private static Expression<Func<T, bool>> CreateExpression<T>(Expression<Func<T, bool>> left, Expression<Func<T, bool>> right, ExpressionType expressionType) where T : class
    {
        return CombineLambdas(left, right, expressionType);
    }

    private static Expression<Func<T, bool>> CombineLambdas<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right, ExpressionType expressionType) where T : class
    {
        if (IsExpressionBodyConstant(left))
            return right;

        var param = left.Parameters.First();
        var visitor = new SubstituteParameterVisitor();
        visitor.Sub[right.Parameters.First()] = param;

        var body = Expression.MakeBinary(expressionType, left.Body, visitor.Visit(right.Body));
        return Expression.Lambda<Func<T, bool>>(body, param);
    }

    private static bool IsExpressionBodyConstant<T>(Expression<Func<T, bool>> left) where T : class
    {
        return left.Body.NodeType == ExpressionType.Constant;
    }

    internal class SubstituteParameterVisitor : ExpressionVisitor
    {
        public Dictionary<Expression, Expression> Sub = new Dictionary<Expression, Expression>();

        protected override Expression VisitParameter(ParameterExpression node)
        {
            Expression newValue;
            if (Sub.TryGetValue(node, out newValue))
                return newValue;

            return node;
        }
    }
}

then use following sample:然后使用以下示例:

Expression<Func<PriceInfo, bool>> predicate = v => true;
        foreach (var item in allowedCategory)
        {
            predicate = predicate.Or(c => c.CategoryName.StartsWith(item.CATEGORY_CODE));
        }
        query = query.Where(predicate);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM