I am using my own extension methods of IQueryable<> to create chainable queries such as FindAll().FindInZip(12345).NameStartsWith(\"XYZ\").OrderByHowIWantIt() etc. which
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 Expression
s 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);
}
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/
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);
Use PredicateBuilder<T>. It's probably what you want.