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

后端 未结 4 2137
不思量自难忘°
不思量自难忘° 2021-02-19 00:08

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 followi

相关标签:
4条回答
  • 2021-02-19 00:42

    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. 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);       
        }
    }
    
    0 讨论(0)
  • 2021-02-19 00:46

    Ok you have had your own share of answer about linq.

    Let me introduce a different approach using 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);
    
    0 讨论(0)
  • 2021-02-19 00:54

    You can use Expression to create in one step like this:

    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:

    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.
    
    0 讨论(0)
  • 2021-02-19 00:55

    First, create some helper extension methods to easier combine two Func<T,bool> predicates:

     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
    
    0 讨论(0)
提交回复
热议问题