问题
I have a DateRange class that I'd like to apply to an IQueryable as a where predicate, automatically using the begin and end dates and automatically using an open or closed interval.
public class DateRange
{
public DateTime? BeginDate { get; set; }
public DateTime? EndDate { get; set; }
public bool BeginInclusive { get; set; }
public bool EndInclusive { get; set; }
public DateRange()
{
BeginInclusive = true;
EndInclusive = false;
}
public IQueryable<T> Apply<T>( IQueryable<T> source, Expression<Func<T,DateTime>> dateField )
{
var result = source;
if (BeginDate.HasValue)
{
if (BeginInclusive)
result = result.Where( x => dateField >= BeginDate ); //does not compile
else
result = result.Where( x => dateField > BeginDate ); //does not compile
}
if (EndDate.HasValue)
{
if (EndInclusive)
result = result.Where( x => dateField <= EndDate ); //does not compile
else
result = result.Where( x => dateField < EndDate ); //does not compile
}
return result;
}
}
And I want to call it like this, DateField is any DateTime property of T.
DateRange d;
IQueryable<T> q;
q = d.Apply( q, x => x.DateField );
So I want to pass a member expression to the Apply method, and have it apply an appropriate where clause to the result set, but I cannot figure out how to get the dateField member expression embedded in the where predicate's expression. See lines "do not compile" in class above. I need to transform dateField somehow or build the predicate expression some other way, but I have no idea how to do so.
回答1:
What you're looking to do here is to compose expressions; you're trying to apply one expression to the result of another. You can actually write a method to do that:
public static Expression<Func<TSource, TResult>> Compose<TSource, TIntermediate, TResult>(
this Expression<Func<TSource, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TSource));
var intermediateValue = first.Body.ReplaceParameter(first.Parameters[0], param);
var body = second.Body.ReplaceParameter(second.Parameters[0], intermediateValue);
return Expression.Lambda<Func<TSource, TResult>>(body, param);
}
It uses the following method to replace the parameter of an expression with an expression.
public static Expression ReplaceParameter(this Expression expression,
ParameterExpression toReplace,
Expression newExpression)
{
return new ParameterReplaceVisitor(toReplace, newExpression)
.Visit(expression);
}
public class ParameterReplaceVisitor : ExpressionVisitor
{
private ParameterExpression from;
private Expression to;
public ParameterReplaceVisitor(ParameterExpression from, Expression to)
{
this.from = from;
this.to = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == from ? to : node;
}
}
This allows you to write your code as:
public IQueryable<T> Apply<T>(IQueryable<T> source,
Expression<Func<T, DateTime>> dateField)
{
var result = source;
if (BeginDate.HasValue)
{
if (BeginInclusive)
result = result.Where(dateField.Compose(date => date >= BeginDate));
else
result = result.Where(dateField.Compose(date => date > BeginDate));
}
if (EndDate.HasValue)
{
if (EndInclusive)
result = result.Where(dateField.Compose(date => date <= EndDate));
else
result = result.Where(dateField.Compose(date => date < EndDate));
}
return result;
}
回答2:
You'll have to hand-craft dateField >= BeginDate
using Expression class methods.
(...)
if (BeginInclusive)
{
var greaterOrEqual =
Expression.Lambda<Func<T, bool>>(
Expression.GreaterThanOrEqual(
dateField.Body,
Expression.Constant(BeginDate)),
dateField.Parameters);
result = result.Where(greaterOrEqual);
}
(...)
Similarly for the other cases.
回答3:
Here's the updated Apply method created after figuring this out.
public IQueryable<T> Apply<T>( IQueryable<T> source, Expression<Func<T,DateTime>> dateField )
{
Expression predicate;
if (BeginDate.HasValue)
{
if (BeginInclusive)
predicate = Expression.GreaterThanOrEqual( dateField.Body, Expression.Constant( BeginDate, typeof(DateTime) ) );
else
predicate = Expression.GreaterThan( dateField.Body, Expression.Constant( BeginDate, typeof(DateTime) ) );
source = source.Where( Expression.Lambda<Func<T, bool>>( predicate ) );
}
if (EndDate.HasValue)
{
if (EndInclusive)
predicate = Expression.LessThanOrEqual( dateField.Body, Expression.Constant( EndDate, typeof(DateTime) ) );
else
predicate = Expression.LessThan( dateField.Body, Expression.Constant( EndDate, typeof(DateTime) ) );
source = source.Where( Expression.Lambda<Func<T, bool>>( predicate ) );
}
return source;
}
Next, I'll transform it into an extension method, so it can be used like:
DateRange range;
IQueryable<T> q;
q = q.WhereInDateRange( range, x => x.DateField );
来源:https://stackoverflow.com/questions/38507906/how-can-i-include-one-expression-in-another-expression