4

I have filters in my asp.net project and want to add expression to this list with condition:

Expression<Func<MyModel, bool>> func;
var list = new List<Expression<Func<MyModel, bool>>>();

I want to conditionally apply Where (OR between them). for example:

if(sth){
   func = p => p.a <= b;
   list.Add(func);
}
if (sth else){
   func = p => p.c >= d;
   list.Add(func);
}

var fq = Session.Query<MyModel>();
fq = list.Aggregate(fq, (current, expression) => current.Where(expression));

How can I do this?

Banafshe Alipour
  • 1,041
  • 4
  • 12
  • 27

4 Answers4

4

Looks like you could do this easily with an extension method

public static class EnumerableExtensions
{
    public static IEnumerable<T> ConditionalWhere<T>(this IEnumerable<T> list, 
                                               bool condition, Func<T,bool> predicate)
    {
        if(!condition)
             return list;
        return list.Where(predicate);
    }
}

Usage would be:

var fq = Session.Query<MyModel>();
var result = fq.ConditionalWhere(sth, p => p.a <= b)
               .ConditionalWhere(sth_else, p => p.c >= d);
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • @fsacer - it DOES work, as the OP didnt really want an OR condition, as such - they wanted to conditionally apply a `Where` clause based on a boolean. This just simplifies that a bit (But thatnks for the DV, without even understanding the answer!) – Jamiec May 09 '16 at 09:18
  • @fsacer Well question was unclear from the start you have to admit that. – fsacer May 09 '16 at 09:28
4

You can build an extension method to merge two condition expressions using OR relationship like this:

public static class Extensions
{
    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
    {
        var parameter = one.Parameters[0];
        var visitor = new ReplaceParameterVisitor(parameter);
        another = (Expression<Func<T, bool>>)visitor.Visit(another);
        var body = Expression.Or(one.Body, another.Body);
        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }
}

class ReplaceParameterVisitor : ExpressionVisitor
{
    public ParameterExpression NewParameter { get; private set; }

    public ReplaceParameterVisitor(ParameterExpression newParameter)
    {
        this.NewParameter = newParameter;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return this.NewParameter;
    }
}

Usage and test code:

Expression<Func<int, bool>> condition1 = x => x > 8;
Expression<Func<int, bool>> condition2 = y => y < 3;            
var condition = condition1.Or(condition2);
var result = Enumerable
    .Range(1, 10)
    .Where(condition.Compile())
    .ToList();      //1,2,9,10
Cheng Chen
  • 42,509
  • 16
  • 113
  • 174
  • Well looks like OP doesn't want OR operation, but conditionally apply Where. – fsacer May 09 '16 at 09:29
  • @Cheng This! Dynamically combining conditions with AND is trivial, but OR... And I managed to do something similar but "manually" building expressions like Expression.Equal(Expression.Property... , Expression.Constant...). This is so much more elegant! Thank you! – Stefan Balan Aug 08 '19 at 09:09
1

I built a bit of code to showcase Predicate<> while trying to stick to your program structure:

using System;
using System.Collections.Generic;
using System.Linq;

namespace SOTests
{
    public class MyModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    class Program
    {
        private static int ControlId;
        private static string ControlName;

        static void Main(string[] args)
        {
            var idPred = new Predicate<MyModel>(m => m.Id > ControlId);
            var namePred = new Predicate<MyModel>(m => m.Name == ControlName);

            var list = new List<MyModel>();

            if (true) // TODO: do id check?
            {
                list = list.Where(m => idPred.Invoke(m)).ToList();
            }

            if (true) // TODO: do name check?
            {
                list = list.Where(m => namePred.Invoke(m)).ToList();
            }

            //var fq = Session.Query<MyModel>();
            //fq = list;
        }
    }
}

I commented out the Session bit not knowing what kind of storage abstraction it represents (and the code wouldn't compile).

The code should explain itself and it's not tested.

It can be much more elegant, but you should state more clearly what your requirements are for that.

WDRust
  • 3,663
  • 1
  • 19
  • 23
0

Thanks all, I found the solution : OrElse

Expression<Func<MyModel, bool>> OrExpressionFunction(Expression<Func<MyModel, bool>> exp1, Expression<Func<MyModel, bool>> exp2)
    {
        ParameterExpression p = exp1.Parameters.Single();
        return Expression.Lambda<Func<MyModel, bool>>(
            Expression.OrElse(exp1.Body, exp2.Body), p);
    }

then:

Expression<Func<MyModel, bool>> MyExp = null;
if(sth){
  func = p => p.a <= b;
  MyExp= OrExpressionFunction(func, MyExp);
}
if (sth else){
  func = p => p.c >= d;
  MyExp= OrExpressionFunction(func, MyExp);
}
list.Add(MyExp);
Banafshe Alipour
  • 1,041
  • 4
  • 12
  • 27