I have one queryable where I have used various Where
and WhereBetween
statements to narrow the collection down to a certain set. Now I need to add kind of a Where || WhereBetween
. In other words, I can't just chain them together like I have up till now, cause that will work as an And. So, how can I do this?
I see two possibilities:
Where
, and one using WhereBetween
. And then concatenate them. Don't know if this is even possible? Also, although not in my particular case, you would most likely end up with duplicates...Where
expression and the expression created in the WhereBetween
with some sort of Or.The first, as mentioned, I am not sure is even possible. And if it was, I'm not so sure it is a good way to do it.
The second, I can see as an option, but not totally sure about all the details. Below is the WhereBetween
method from my other question, which I now use and it works great:
public static IQueryable<TSource> WhereBetween<TSource, TValue>(
this IQueryable<TSource> source,
Expression<Func<TSource, TValue>> selector,
IEnumerable<Range<TValue>> ranges)
{
var param = Expression.Parameter(typeof(TSource), "x");
var member = Expression.Invoke(selector, param);
Expression body = null;
foreach (var range in ranges)
{
var filter = Expression.AndAlso(
Expression.GreaterThanOrEqual(member,
Expression.Constant(range.A, typeof(TValue))),
Expression.LessThanOrEqual(member,
Expression.Constant(range.B, typeof(TValue))));
body = body == null ? filter : Expression.OrElse(body, filter);
}
return body == null ? source : source.Where(
Expression.Lambda<Func<TSource, bool>>(body, param));
}
I'm thinking that I could maybe extract the expression building portion of it into a new method. Perhaps like this:
public static IQueryable<TSource> WhereBetween<TSource, TValue>(
this IQueryable<TSource> source,
Expression<Func<TSource, TValue>> selector,
IEnumerable<Range<TValue>> ranges)
{
return source.Where(WhereBetween(selector, ranges));
}
public static Expression<Func<TSource, bool>> WhereBetween<TSource, TValue>(
Expression<Func<TSource, TValue>> selector,
IEnumerable<Range<TValue>> ranges)
{
var param = Expression.Parameter(typeof(TSource), "x");
var member = Expression.Invoke(selector, param);
Expression body = null;
foreach (var range in ranges)
{
var filter = Expression.AndAlso(
Expression.GreaterThanOrEqual(member,
Expression.Constant(range.A, typeof(TValue))),
Expression.LessThanOrEqual(member,
Expression.Constant(range.B, typeof(TValue))));
body = body == null ? filter : Expression.OrElse(body, filter);
}
return body == null
? ø => true
: Expression.Lambda<Func<TSource, bool>>(body, param);
}
I could then use that new method to get the expression instead of the queryable. So, lets say I have the WhereBetween(ø => ø.Id, someRange)
and for example ø => ø.SomeValue == null
. How can I combine those two with Or? I'm looking at the Expression.OrElse
used in the WhereBetween
method, and I think that might be what I need, or maybe this the Expression.Or
. But I'm very unstable on this expression stuff, so I am not sure what to choose here, or even if I am on the right track :p
Could someone give me some pointers here?
You have two options here - Queryable.Union
, or expression combination. I'd generally favor the latter, via OrElse
- which (with LINQ-to-SQL at least) you can do with 2 expressions (see below) - but in either case it should get composed:
using(var ctx = new DataClasses1DataContext())
{
ctx.Log = Console.Out;
Expression<Func<Customer, bool>> lhs =
x => x.Country == "UK";
Expression<Func<Customer, bool>> rhs =
x => x.ContactName.StartsWith("A");
var arr1 = ctx.Customers.Where(
lhs.OrElse(rhs)).ToArray();
var arr2 = ctx.Customers.Where(lhs)
.Union(ctx.Customers.Where(rhs)).ToArray();
}
Both arr1
and arr2
each only perform 1 database hit (although the TSQL is different; the first has an OR
in the WHERE
clause; the second has two separate queries with UNION
).
Here's the extension method I used:
static Expression<Func<T, bool>> OrElse<T>(
this Expression<Func<T, bool>> lhs,
Expression<Func<T, bool>> rhs)
{
var row = Expression.Parameter(typeof(T), "row");
var body = Expression.OrElse(
Expression.Invoke(lhs, row),
Expression.Invoke(rhs, row));
return Expression.Lambda<Func<T, bool>>(body, row);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With