Extend IQueryable <T> Where () as OR instead of AND relationship
I use my own IQueryable <> extension methods to create chained queries like FindAll (). FindInZip (12345) .NameStartsWith ("XYZ"). OrderByHowIWantIt (), etc., which then, when delayed, create one request based on my chain of extension methods.
The problem with this is that everything where in the extension chain (FindXYZ, FindInZip, etc.) will always be combined as AND, which means that I cannot do something like this:
FindAll (). FirstNameStartsWith ("X"). OrLastNameStartsWith ("Z") because I do not know how I can enter OR in a separate Where method.
Any idea how I can solve this?
additional; Until now, I understand how chains are expressed as or if I wrap them (for example, CompileAsOr (FirstNameStartsWith ("A"). LastNameStartsWith ("Z"). OrderBy (..))
What I'm trying to do is a bit more complicated (and the PredicateBuilder doesn't help here ..) in that I want the later IQueryable to basically have access to the Where conditions that were set earlier without having to wrap them Or between them.
How each extension method returns IQueryable <> I understand that it should have information about the current state of the query conditions somewhere, which makes me think that there should be some kind of automated way to either create or in all the previous ones. Where are the conditions without to wrap what you want Or'd.
I assume that the different parts of the request are known only at runtime, i.e. you can't just use || in where ...
One lazy version of Concat - but this leads to poor TSQL, etc .; however, I tend to write Expression instead. The selection approach depends on what the provider is, since LINQ-to-SQL supports various EF variants (for example) - this has a real impact here (since you cannot use subexpressions with EF). Can you tell us that?
Here is the code that should work with LINQ-to-SQL; if you create an array (or list and call .ToArray() ) of the expressions, it should work fine; For example, LINQ-to-Objects, but it should 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); } 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 will be the simplest and most readable. However, this does not compile.
operator '||' cannot be applied to operands like "
Expression<Func<YourClass,bool>>" and "Expression<Func<YourClass,bool>>"
Therefore, I use the extension method for this. In your example, it will look like this: .Where(FindInZip(12345).Or(NameStartsWith("XYZ")).And(PostedOnOrAfter(DateTime.Now)) .
Instead:
.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 } Really nice article on LINQ Queries by Extending Expressions. Also the source of the extension method that I use.