skip to Main Content

My problem is a bit more complicated, so I have simplified it with the below example.
 

Let’s say I have a simple list of Integers. I receive two condition input parameters in my function which tell me how to filter. Something like this:

 public class input{
       public string operation = "less than";//can be <=, >=, !=, ==,<. >, etc. 
       public integer filter = 9;
}

I would like to dynamically filter based on the conditions passed in, but I need to have it as an "OR" clause.

How can I dynamically create a linq query, without hard coding every single combination like this:

/*
input one = { operation = "less than" , filter = 9 }
input two = { operation = "equal to", filter =10 }
arraytoFilter { 1,2,3,4,5,6,7,8,9,10,11,12 }
*/
public int[] myFilter(input one, input two, int[] arraytoFilter){

 if (one.operation == "less than" && two.operation == "equal to"){

    return arraytoFilter.Where(x => x < one.filter || x == two.filter)

   }

    if (one.operation == "less than or equal to" && two.operation == "greater than"){

    return arraytoFilter.Where(x => x <= one.filter || x > two.filter)

   }
 /// rest of all combinations (how do I avoid doing this???)
    return arrayToFilter;
}

2

Answers


  1. [ UPDATE 11/20 ]

    This can be done without NuGet packages by building expressions using System.Linq.Expressions:

    using System.Linq.Expressions;
    
    class Program
    {
        public class input
        {
            public string? operation { get; set; }
            public int filter { get; set; }
        }
    
        static void Main()
        {
            int[] arrayToFilter = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
    
            input one = new()
            {
                operation = "less than",
                filter = 9
            };
    
            input two = new()
            {
                operation = "equal to",
                filter = 10
            };
    
            var filteredArray = myFilter(one, two, arrayToFilter);
    
            foreach (var number in filteredArray)
            {
                Console.WriteLine(number);
            }
        }
    
        static int[] myFilter(input one, input two, int[] arrayToFilter)
        {
            var parameter = Expression.Parameter(typeof(int), "num");
            var firstComparisonExpression = GetComparisonExpression(parameter, one.operation, one.filter);
            var secondComparisonExpression = GetComparisonExpression(parameter, two.operation, two.filter);
    
            // Short-circuiting OR
            var orElseExpression = Expression.OrElse(firstComparisonExpression, secondComparisonExpression);
    
            var lambda = Expression.Lambda<Func<int, bool>>(orElseExpression, parameter);
            
            return arrayToFilter.AsQueryable().Where(lambda.Compile()).ToArray();
        }
    
        static Expression GetComparisonExpression(ParameterExpression parameter, string? operation, int filter)
        {
            switch (operation)
            {
                case "less than":
                    return Expression.LessThan(parameter, Expression.Constant(filter));
                case "less than or equal to":
                    return Expression.LessThanOrEqual(parameter, Expression.Constant(filter));
                case "greater than":
                    return Expression.GreaterThan(parameter, Expression.Constant(filter));
                case "greater than or equal to":
                    return Expression.GreaterThanOrEqual(parameter, Expression.Constant(filter));
                case "equal to":
                    return Expression.Equal(parameter, Expression.Constant(filter));
                case "not equal to":
                    return Expression.NotEqual(parameter, Expression.Constant(filter));
                default:
                    throw new ArgumentException("Invalid operation phrase.");
            }
        }
    }
    

    You can use the System.Linq.Dynamic.Core package to write a dynamic LINQ query. Program below was tested with version 1.3.5:

    using System.Linq.Dynamic.Core;
    
    class Program
    {
        public class input
        {
            public string? operation { get; set; }
            public int filter { get; set; }
        }
    
        static void Main()
        {
            int[] arrayToFilter = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
    
            input one = new()
            {
                operation = "less than",
                filter = 9
            };
    
            input two = new()
            {
                operation = "equal to",
                filter = 10
            };
    
            var filteredArray = myFilter(one, two, arrayToFilter);
    
            foreach (var number in filteredArray)
            {
                Console.WriteLine(number);
            }
        }
    
        static int[] myFilter(input one, input two, int[] arrayToFilter)
        {
            var firstSymbol = ConvertOperationToSymbol(one.operation);
            var secondSymbol = ConvertOperationToSymbol(two.operation);
    
            string dynamicQuery = $"it {firstSymbol} {one.filter} || it {secondSymbol} {two.filter}";
    
            return arrayToFilter.AsQueryable().Where(dynamicQuery).ToArray();
        }
    
        static string ConvertOperationToSymbol(string? operation)
        {
            switch (operation)
            {
                case "less than":
                    return "<";
                case "less than or equal to":
                    return "<=";
                case "greater than":
                    return ">";
                case "greater than or equal to":
                    return ">=";
                case "equal to":
                    return "==";
                case "not equal to":
                    return "!=";
                default:
                    throw new ArgumentException("Invalid operation phrase.");
            }
        }
    }
    
    Login or Signup to reply.
  2. You should need the almighty System.Linq.Expressions to construct the expression tree and filter.

    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    
    public static int[] BuildFilter(int[] source, params Input[] inputs)
    {
        var param = Expression.Parameter(typeof(int), "x");
    
        List<BinaryExpression> expressions = new List<BinaryExpression>();
        BinaryExpression combineExpression = null;
    
        if (inputs != null && inputs.Any())
        {
            foreach (Input input in inputs)
            {
                var constant = Expression.Constant(input.filter);
    
                switch (input.operation)
                {
                    case "equal to":
                        expressions.Add(Expression.Equal(param, constant));
                        break;
    
                    case "not equal":
                        expressions.Add(Expression.NotEqual(param, constant));
                        break;
    
                    case "less than":
                        expressions.Add(Expression.LessThan(param, constant));
                        break;
    
                    case "less than or equal to":
                        expressions.Add(Expression.LessThanOrEqual(param, constant));
                        break;
    
                    case "greater than":
                        expressions.Add(Expression.GreaterThan(param, constant));
                        break;
    
                    case "greater than or equal to":
                        expressions.Add(Expression.GreaterThanOrEqual(param, constant));
                        break;
    
                    default:
                        throw new Exception("Operation is not supported");
                }           
            }
    
            combineExpression = expressions[0];
    
            foreach (var expr in expressions.Skip(1))
            {
                combineExpression = Expression.OrElse(combineExpression, expr);
            }
        }
    
        var lambda = combineExpression != null
            ? Expression.Lambda<Func<int, bool>>(combineExpression, param)
            : Expression.Lambda<Func<int, bool>>(Expression.Constant(true), param);
    
        return source.Where(lambda.Compile())
            .ToArray();
    }
    

    Caller method:

    Input[] input = new Input[]
    {
        new Input { operation = "less than" , filter = 9 },
        new Input { operation = "equal to", filter = 10 }
    };
    int[] arraytoFilter  = new int[] { 1,2,3,4,5,6,7,8,9,10,11,12 };
    
    var result = BuildFilter(arraytoFilter, input);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search