Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Dynamic Or Expressions in C#

I use the dynamic filter in my program like following solution:

public static IQueryable<T> MyFilter<T>(this IQueryable<T> queryable) where T : class ,IModel
{
        var someIds = new int[]{1,2,3,4,5};
        var userId = 2;

        Expression<Func<T, bool>> predicate1 = e => someIds.Contains(e.Id);
        Expression<Func<T, bool>> predicate2 = e => e.UserId==userId;
        Expression<Func<T, bool>> predicate3 = e => e.CreatedDate != null;

        var pred1 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(predicate1, predicate2));
        var pred2 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(pred1, predicate3));


        var result = queryable
            .Where(pred2);

        return result;
    }

The IModel is like this:

Public interface IModel{
   int Id { get; set; }
   int UserId { get; set; }
   DateTime? CreatedDate { get; set; }
}

but, I get this error:

The binary operator Or is not defined for the types 'System.Func2[AnyClass,System.Boolean]' and 'System.Func2[AnyClass,System.Boolean]'.

at this line:

var pred1 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(predicate1, predicate2));

How can I solve this problem?

beforehand appreciate any help

like image 469
Pedram Avatar asked Oct 17 '25 16:10

Pedram


2 Answers

As I've written in my comments, there are two different problems:

  • You must use Expression.OrElse, because Expression.Or is the | operator.

  • BUT the real problem is that you can't join Lambda expressions (predicate1, 2, 3) in that way. Joining expressions is complex, because the input parameters for predicate1, 2, 3 is different (it's like it is e1, e2, e3 instead of e), and pred1 and pred2 will have an e4 and e5 input parameter, so you have to to some expression replacing.

The solution:

// Note that we only need the Body!
Expression pred1 = Expression.OrElse(Expression.OrElse(predicate1.Body, predicate2.Body), predicate3.Body);

// We change all the predicate2.Parameters[0] to predicate1.Parameters[0] and
// predicate3.Parameters[0] to predicate1.Parameters[0]

var replacer = new SimpleExpressionReplacer(
    /* from */ new[] { predicate2.Parameters[0], predicate3.Parameters[0] }, 
    /* to */ new[] { predicate1.Parameters[0], predicate1.Parameters[0] });

pred1 = replacer.Visit(pred1);

// We use for the new predicate the predicate1.Parameters[0]
var pred2 = Expression.Lambda<Func<T, bool>>(pred1, predicate1.Parameters[0]);

var result = queryable.Where(pred2);

and the SimpleExpressionReplacer:

// A simple expression visitor to replace some nodes of an expression 
// with some other nodes
public class SimpleExpressionReplacer : ExpressionVisitor
{
    public readonly Dictionary<Expression, Expression> Replaces;

    public SimpleExpressionReplacer(Dictionary<Expression, Expression> replaces)
    {
        Replaces = replaces;
    }

    public SimpleExpressionReplacer(IEnumerable<Expression> from, IEnumerable<Expression> to)
    {
        Replaces = new Dictionary<Expression, Expression>();

        using (var enu1 = from.GetEnumerator())
        using (var enu2 = to.GetEnumerator())
        {
            while (true)
            {
                bool res1 = enu1.MoveNext();
                bool res2 = enu2.MoveNext();

                if (!res1 || !res2)
                {
                    if (!res1 && !res2)
                    {
                        break;
                    }

                    if (!res1)
                    {
                        throw new ArgumentException("from shorter");
                    }

                    throw new ArgumentException("to shorter");
                }

                Replaces.Add(enu1.Current, enu2.Current);
            }
        }
    }

    public SimpleExpressionReplacer(Expression from, Expression to)
    {
        Replaces = new Dictionary<Expression, Expression> { { from, to } };
    }

    public override Expression Visit(Expression node)
    {
        Expression to;

        if (node != null && Replaces.TryGetValue(node, out to))
        {
            return base.Visit(to);
        }

        return base.Visit(node);
    }
}
like image 65
xanatos Avatar answered Oct 20 '25 06:10

xanatos


Use PredicateBuilder

Either copy the source of the class from here, or install the LINQKit NuGet package (which has a dependency on Entity Framework), to get the PredicateBuilder class. It's simple to dynamically chain expression together with it:

var predicate = PredicateBuilder.False<int>();
predicate = predicate.Or(x => x > 5);
predicate = predicate.Or(x => x % 2 == 0);
predicate = predicate.Or(x => x == 42 );

var queryable = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.AsQueryable();
var result = queryable.Where(predicate);
like image 20
Allon Guralnek Avatar answered Oct 20 '25 04:10

Allon Guralnek