I have an IEnumerable of a class that I created to contain date ranges. That class looks like this:
public class Range<T>
where T: struct
{
public T Start { get; set; }
public T End { get; set; }
}
I want to find all the records in my set where a date column falls within ANY of the specified date ranges. This was my attempt:
deals = deals.Where(
deal =>
criteria.DateRanges.Any(
dt =>
deal.CloseDate >= dt.Start &&
deal.CloseDate < dt.End.Value.AddDays(1)));
This throws an error I assume because EF doesn't know how to translate criteria.DateRanges.Any()
to SQL. So how would you write this to find dates that match ANY of the date ranges?
You can use LinqKit for this:
var expr = PredicateBuilder.False<Deal>();
foreach(var range in criteria.DateRanges)
expr = expr.Or(d => dt.CloseDate >= range.Start && dt.CloseDate < range.End);
deals = deals.AsExpandable().Where(expr);
Another option would be to use Expression Trees but that seems a bit overkill for what you're trying to do.
Agreed with @stuartd, if there are a few range conditions you can build your own expression creating one And
expression per range, and combining them at the end in several Ors
. You could create a static generic method like this:
public static Expression<Func<T,bool>> RangeExpression<T>(string property, IEnumerable<Range<DateTime>> criterias )
{
Expression result = null;
ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "o");
foreach (var item in criterias)
{
var value1 = item.Start.Date;
var value2 = item.End.Date.AddDays(1);
MemberExpression memberExpression1 = Expression.PropertyOrField(parameterExpression, property);
MemberExpression memberExpression2 = Expression.PropertyOrField(parameterExpression, property);
ConstantExpression valueExpression1 = Expression.Constant(value1, typeof(DateTime));
ConstantExpression valueExpression2 = Expression.Constant(value2, typeof(DateTime));
BinaryExpression binaryExpression1 = Expression.GreaterThanOrEqual(memberExpression1, valueExpression1);
BinaryExpression binaryExpression2 = Expression.LessThan(memberExpression2, valueExpression2);
var ret1 = Expression.Lambda<Func<T, bool>>(binaryExpression1, parameterExpression);
var ret2 = Expression.Lambda<Func<T, bool>>(binaryExpression2, parameterExpression);
Expression and = Expression.And(ret1, ret2);
result = result!=null?Expression.OrElse(result, and):and;
}
return Expression.Lambda < Func<T,bool>>(result, parameterExpression);
}
And later in your code, you could do something like this:
var rangeExp=RangeExpression<Deal>("CloseDate",criteria.DateRanges);
deals = deals.Where(rangeExp);