【问题标题】:How can I include one expression in another expression?如何在另一个表达式中包含一个表达式?
【发布时间】:2016-07-21 15:08:14
【问题描述】:

我有一个 DateRange 类,我想将它作为 where 谓词应用于 IQueryable,自动使用开始日期和结束日期,并自动使用开放或封闭间隔。

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

我想这样称呼它,DateField 是 T 的任何 DateTime 属性。

DateRange d;
IQueryable<T> q;
q = d.Apply( q, x => x.DateField );

所以我想将成员表达式传递给 Apply 方法,并让它将适当的 where 子句应用于结果集,但我不知道如何获取嵌入在 where 中的 dateField 成员表达式em> 谓词的表达式。请参阅上面类中的“不编译”行。我需要以某种方式转换 dateField 或以其他方式构建谓词表达式,但我不知道该怎么做。

【问题讨论】:

  • 您必须使用 Expression 类方法手工制作 dateField &gt;= BeginDate
  • 我不知道该怎么做。我不太使用 Expression 类本身。我使用过 LinqKit 的 PredicateBuilder,但在这种情况下并没有多大帮助。最后的 'where' 谓词也必须在 Linq-to-Entities 中起作用。
  • 这个样子怎么样:var expr = Expression.GreaterThanOrEqual( dateField.Body, Expression.Constant( BeginDate ) ); result = result.Where( Expression.Lambda&lt;Func&lt;T, bool&gt;&gt;( expr ) );
  • 我很确定您需要将参数传递给Expression.Lambda。当我尝试时,您的代码会引发异常。看看我的回答。

标签: c# lambda expression


【解决方案1】:

您在这里要做的是编写表达式;您正在尝试将一个表达式应用于另一个表达式的结果。您实际上可以编写一个方法来做到这一点:

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

它使用以下方法将表达式的参数替换为表达式。

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

这允许您将代码编写为:

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

【讨论】:

  • 谢谢。你为我节省了一个小时甚至更多的时间来实现它。可惜 BCL 没有提供这种方法
【解决方案2】:

您必须使用 Expression 类方法手工制作dateField &gt;= BeginDate

(...)
    if (BeginInclusive)
    {
        var greaterOrEqual =
            Expression.Lambda<Func<T, bool>>(
                Expression.GreaterThanOrEqual(
                    dateField.Body,
                    Expression.Constant(BeginDate)),
                dateField.Parameters);

        result = result.Where(greaterOrEqual);
    }
(...)

其他情况类似。

【讨论】:

  • 太棒了,这正是我想出的。我也将用最终代码发布另一个答案,以便人们可以使用它。我只需要确认这一切都可以按预期与 Linq-to-Entities 一起使用。
  • 我正在尝试使用IEnumerable&lt;T&gt; 进行自己的学习,但我无法编译它,你能看到我错过了什么吗? public IEnumerable&lt;T&gt; Apply&lt;T&gt;(IEnumerable&lt;T&gt; source, Func&lt;IEnumerable&lt;T&gt;, DateTime, bool&gt; dateField) { var result = source; return result.Where(x=&gt;dateField(source,x)); }
【解决方案3】:

这是解决此问题后创建的更新的 Apply 方法。

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

接下来,我将它转换成一个扩展方法,所以它可以像这样使用:

DateRange range;
IQueryable<T> q;
q = q.WhereInDateRange( range, x => x.DateField );

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-13
    • 2011-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多