Question

I am using my own extension methods of IQueryable<> to create chainable queries such as FindAll().FindInZip(12345).NameStartsWith("XYZ").OrderByHowIWantIt() etc. which then on deferred execution creates a single query based on my extension methods chain.

The problem with this though, is that all Where's in the extension chain (FindXYZ, FindInZip etc.) will always combine as AND which means I can't do something like this:

FindAll().FirstNameStartsWith("X").OrLastNameStartsWith("Z") because I don't know how I can inject the OR in a separate Where method.

Any idea how I can solve this?


additional; So far I understand how to chain expressions as Or if I wrap them (e.g. CompileAsOr(FirstNameStartsWith("A").LastNameStartsWith("Z").OrderBy(..))

What I'm trying to do though is slightly more complicated (and PredicateBuilder doesn't help here..) in that I want a later IQueryable to basically access the Where conditions that were established prior without having to wrap them to create the Or between them.

As each extension method returns IQueryable<> I understand that it should have the knowledge about the current status of query conditions somewhere, which leads me to believe that there should be some automated way or creating an Or across all prior Where conditions without having to wrap what you want Or'd.

Was it helpful?

Solution

I'm assuming the different parts of the query are only known at runtime, i.e. you can't just use || in a where...

One lazy option is Concat - but this tends to lead to poor TSQL etc; however, I tend to be inclined to write custom Expressions instead. The approach to take depends on what the provider is, as LINQ-to-SQL supports different options to EF (for example) - which has a genuine impact here (since you can't use sub-expressions with EF). Can you tell us which?


Here's some code that should work with LINQ-to-SQL; if you build an array (or list, and call .ToArray()) of expressions, it should work fine; example is LINQ-to-Objects, but should still work:

    static void Main()
    {
        var data = (new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).AsQueryable();

        var predicates = new List<Expression<Func<int, bool>>>();
        predicates.Add(i => i % 3 == 0);
        predicates.Add(i => i >= 8);           

        foreach (var item in data.WhereAny(predicates.ToArray()))
        {
            Console.WriteLine(item);
        }
    }

    public static IQueryable<T> WhereAny<T>(
        this IQueryable<T> source,
        params Expression<Func<T,bool>>[] predicates)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (predicates == null) throw new ArgumentNullException("predicates");
        if (predicates.Length == 0) return source.Where(x => false); // no matches!
        if (predicates.Length == 1) return source.Where(predicates[0]); // simple

        var param = Expression.Parameter(typeof(T), "x");
        Expression body = Expression.Invoke(predicates[0], param);
        for (int i = 1; i < predicates.Length; i++)
        {
            body = Expression.OrElse(body, Expression.Invoke(predicates[i], param));
        }
        var lambda = Expression.Lambda<Func<T, bool>>(body, param);
        return source.Where(lambda);
    }

OTHER TIPS

Use PredicateBuilder<T>. It's probably what you want.

    List<string> fruits =
        new List<string> { "apple", "passionfruit", "banana", "mango",
               "orange", "blueberry", "grape", "strawberry" };

    var query = fruits.AsQueryable();

    // Get all strings whose length is less than 6.
    query = query.Where(fruit => fruit.Length < 6);

    // Hope to get others where length is more than 8.  But you can't, they're gone.
    query = query.Where(fruit => 1 == 1 || fruit.Length > 8);

    foreach (string fruit in query)
        Console.WriteLine(fruit);

In an ideal world I personally think || and && operators would be the most simple and readable. However it won't compile.

operator ' ||' cannot be applied to operands of type 'Expression<Func<YourClass,bool>>' and 'Expression<Func<YourClass,bool>>'

Therefore I use an extension method for this. In your example it would look like this: .Where(FindInZip(12345).Or(NameStartsWith("XYZ")).And(PostedOnOrAfter(DateTime.Now)).

Instead of:

.Where(FindInZip(12345) || NameStartsWith("XYZ") && (PostedOnOrAfter(DateTime.Now)).

Expression example:

private Expression<Func<Post,bool>> PostedOnOrAfter(DateTime cutoffDate)
{
      return post => post.PostedOn >= cutoffDate;
};

Extension method:

public  static  class PredicateExtensions
{
     ///  <summary>
     /// Begin an expression chain
     ///  </summary>
     ///  <typeparam id="T""></typeparam>
     ///  <param id="value"">Default return value if the chanin is ended early</param>
     ///  <returns>A lambda expression stub</returns>
     public  static Expression<Func<T,  bool>> Begin<T>(bool value =  false)
    {
         if (value)
             return parameter =>  true;  //value cannot be used in place of true/false

         return parameter =>  false;
    }

     public  static Expression<Func<T,  bool>> And<T>(this Expression<Func<T,  bool>> left,
        Expression<Func<T,  bool>> right)
    {
         return CombineLambdas(left, right, ExpressionType.AndAlso);
    }

     public  static Expression<Func<T,  bool>> Or<T>(this Expression<Func<T,  bool>> left, Expression<Func<T,  bool>> right)
    {
         return CombineLambdas(left, right, ExpressionType.OrElse);
    }

     #region private

     private  static Expression<Func<T,  bool>> CombineLambdas<T>(this Expression<Func<T,  bool>> left,
        Expression<Func<T,  bool>> right, ExpressionType expressionType)
    {
         //Remove expressions created with Begin<T>()
         if (IsExpressionBodyConstant(left))
             return (right);

        ParameterExpression p = left.Parameters[0];

        SubstituteParameterVisitor visitor =  new SubstituteParameterVisitor();
        visitor.Sub[right.Parameters[0]] = p;

        Expression body = Expression.MakeBinary(expressionType, left.Body, visitor.Visit(right.Body));
         return Expression.Lambda<Func<T,  bool>>(body, p);
    }

     private  static  bool IsExpressionBodyConstant<T>(Expression<Func<T,  bool>> left)
    {
         return left.Body.NodeType == ExpressionType.Constant;
    }

     internal  class SubstituteParameterVisitor : ExpressionVisitor
    {
         public Dictionary<Expression, Expression> Sub =  new Dictionary<Expression, Expression>();

         protected  override Expression VisitParameter(ParameterExpression node)
        {
            Expression newValue;
             if (Sub.TryGetValue(node,  out newValue))
            {
                 return newValue;
            }
             return node;
        }
    }

     #endregion
} 

A really good article about LINQ Queries by Extending Expressions. Also the source of the extension method that I use.

https://www.red-gate.com/simple-talk/dotnet/net-framework/giving-clarity-to-linq-queries-by-extending-expressions/

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top