Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic OR in LINQ without the PredicateBuilder

Tags:

I am building a method that takes one or more criteria for querying a database with LINQ. I made this:

public ICollection<MyClass> FindAllBy(params Expression<Func<MyClass, bool>>[] criteria)
    {
        using (var ctx = new MyContext())
        {
            IQueryable<MyClass> result = ctx.MyClasses.AsNoTracking();

            if (criteria != null && criteria.Length > 0)
            {
                foreach (var item in criteria)
                {
                    result = result.Where(item);
                }
            }

            return result.ToList();
        }
    }

This has the effect that if I look for a object with Id 1 and one with Id 2 I get nothing, as no row has both an Id of 1 and 2. So I need an OR clause. I found this page:

http://www.albahari.com/nutshell/predicatebuilder.aspx

Which has a PredicateBuilder class, which I used to make this:

    public ICollection<PhotoFile> FindAllBy(params Expression<Func<PhotoFile, bool>>[] criteria)
    {
        using (var ctx = new CotanContext())
        {
            var predicate = PredicateBuilder.False<PhotoFile>();

            if (criteria != null && criteria.Length > 0)
            {
                foreach (var item in criteria)
                {
                    predicate = predicate.Or(item);
                }
            }

            return ctx.PhotoFiles.Where(predicate).ToList();
        }
    }

My code differs slightly from the page in that I pass in an expression into the method, which I then pass into the Predicate.Or method.

The above method gives a The LINQ expression node type 'Invoke' is not supported in LINQ to Entities. error. Which makes sense, as Entity Framework does not know how to translate this code into a valid query.

The solution on the linked site is to download their Nuget package or source code, which makes this code work. However, I don't really feel comfortable putting in several hundreds of lines of unknown and seemingly untested code for a single function, that in my opinion should have been built into LINQ ages ago by Microsoft. And the lead developer on my project has also in the past strongly advised against using unknown packages that aren't directly from Microsoft. I am working with sensitive information, so I would rather be safe than sorry.

So, my question: is there any way to get an OR function in LINQ without having to use an external Nuget package?

like image 747
yesman Avatar asked Jun 29 '16 07:06

yesman


1 Answers

As I mentioned in the comments, you can use the Universal PredicateBulder or the class from my answer to Establish a link between two lists in linq to entities where clause.

However you can greatly simplify the methods like the one from your example by using this simple extension method:

public static class QueryableExtensions
{
    public static IQueryable<T> WhereAny<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, bool>>> predicates)
    {
        if (predicates == null || !predicates.Any()) return source;
        var predicate = predicates.Aggregate((a, b) => Expression.Lambda<Func<T, bool>>(
            Expression.OrElse(a.Body, b.Body.ReplaceParameter(b.Parameters[0], a.Parameters[0])),
            a.Parameters[0]));
        return source.Where(predicate);
    }
}

which in turn uses this helper:

public static class ExpressionUtils
{
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
    {
        return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
    }

    class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == Source ? Target : base.VisitParameter(node);
        }
    }
}

Now the sample method could be simple as that:

public ICollection<PhotoFile> FindAllBy(params Expression<Func<PhotoFile, bool>>[] criteria)
{
    using (var ctx = new CotanContext())
        return ctx.PhotoFiles.WhereAny(criteria).ToList();
}
like image 115
Ivan Stoev Avatar answered Sep 28 '22 03:09

Ivan Stoev