How can I include one expression in another expression?

后端 未结 3 1021
悲哀的现实
悲哀的现实 2021-01-21 05:07

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 int

相关标签:
3条回答
  • 2021-01-21 05:39

    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.

    0 讨论(0)
  • 2021-01-21 05:42

    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;
    }
    
    0 讨论(0)
  • 2021-01-21 05:51

    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 );
    
    0 讨论(0)
提交回复
热议问题