Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
378 views
in Technique[技术] by (71.8m points)

c# - How can I include one expression in another expression?

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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;
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...