3

Hello i try to generate a single Func from a list combined by or.

var funcs = new List<Func<User, bool>>()
{
    (u) => u.Id.Equals(entityToFind.Id),
    (u) => u.UserName == entityToFind.UserName,
    (u) => u.Email == entityToFind.Email
};

//TODO: Some magic that funs is euqaly to that:

Func<User, bool> func =  (u) => u.Id.Equals(entityToFind.Id) || u.UserName == entityToFind.UserName || u.Email == entityToFind.Email;

I also tried it with Expressions, like that:

private Dictionary<string, Expression<Func<User, bool>>>     private Dictionary<string, Expression<Func<User, bool>>> test(User entityToFind)
{
    return new Dictionary<string, Expression<Func<User, bool>>>() {
        {"Id", (u) => u.Id.Equals(entityToFind.Id) },
        {"Name", (u) => u.UserName == entityToFind.UserName },
        {"Email", (u) => u.Email == entityToFind.Email }
    };
}


public static Expression<Func<T, bool>> ToOrExpression<T>(this Dictionary<string, Expression<Func<T, bool>>> dict)
{
    var expressions = dict.Values.ToList();
    if (!expressions.Any())
    {
        return t => true;
    }

    var delegateType = typeof(Func<T, bool>)
        .GetGenericTypeDefinition()
        .MakeGenericType(new[]
            {
                typeof(T),
                typeof(bool)
            }
        );

    var tfd = Expression.OrElse(expressions[0], expressions[1]);

    var combined = expressions
        .Cast<Expression>()
        .Aggregate( (e1, e2) => Expression.OrElse(e1, e2) );

    return (Expression<Func<T, bool>>)Expression.Lambda(delegateType, combined);
}


test(entityToFind).ToOrExpression();

But there i will get the following error:

The binary operator OrElse is not defined for the types 'System.Func2[Models.User,System.Boolean]' and 'System.Func2[Models.User,System.Boolean]'

Flo
  • 1,179
  • 3
  • 15
  • 43

1 Answers1

7

While you could create a wrapper method to combine a bunch of Funcs, because you are using Entity Framework, that would cause the entire dataset to be downloaded into memory and the search done locally. What you should be using is Expression<Func<T, bool>> instead.

Fortunately Marc Gravell has already written a handy bit of code to combine expressions. Your question is strictly a dupliucate because you want to combine more than 2 together, but that is quite easy with a little Linq. So, lets start with your expressions first, the code barely changes:

var expressions = new List<Expression<Func<User, bool>>>()
{
    (u) => u.Id.Equals(entityToFind.Id),
    (u) => u.UserName == entityToFind.UserName,
    (u) => u.Email == entityToFind.Email
};

Now using Marc's code and modifying it to be or instead of and:

public static class ExpressionExtensions
{
    public static Expression<Func<T, bool>> OrElse<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        var parameter = Expression.Parameter(typeof(T));

        var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
        var left = leftVisitor.Visit(expr1.Body);

        var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
        var right = rightVisitor.Visit(expr2.Body);

        return Expression.Lambda<Func<T, bool>>(
            Expression.OrElse(left, right), parameter);
    }

    private class ReplaceExpressionVisitor
    : ExpressionVisitor
    {
        private readonly Expression _oldValue;
        private readonly Expression _newValue;

        public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
        {
            _oldValue = oldValue;
            _newValue = newValue;
        }

        public override Expression Visit(Expression node)
        {
            if (node == _oldValue)
                return _newValue;
            return base.Visit(node);
        }
    }
}

No you combine your expressions with the Linq Aggregate method:

var combinedExpression = expressions.Aggregate((x, y) => x.OrElse(y));

And use it something like this:

var result = db.Things.Where(combinedExpression);
DavidG
  • 113,891
  • 12
  • 217
  • 223
  • 2
    WOW! Thank you very much! That is working, now i only try to get how this is working... ;) – Flo Sep 05 '18 at 17:19