在LinQ表达式树中声明变量

时间:2014-02-03 11:12:46

标签: c# .net linq lambda expression

在上一个问题(Refine Enumerable LINQ WHERE statement with repeated parameters)中,我通过使用let语句声明变量来解决问题,我的代码如下所示:

return
from q in pList
let currentContract = q.StaffContracts.OrderByDescending(p => p.SignedDate).Where(p => p.Active).FirstOrDefault()
where (currentContract.Timespan >= fromValue && currentContract.Timespan <= toValue)
select q;

在这种情况下,currentContract是重复的表达式。现在我遇到了一个新问题。我的方法不再提供或请求IQueryable,但它需要Expression<Func<Staff, bool>>作为返回(意思是,只有WHERE子句)。

我尝试了这个但是没有成功:

return q =>
{
    var currentContract = q.StaffContracts.OrderByDescending(p => p.SignedDate).Where(p => p.Active).FirstOrDefault();
    return currentContract != null && currentContract.Timespan >= fromValue && currentContract.Timespan <= toValue;
};

编译错误消息是:

  

带有语句主体的lambda表达式无法转换为表达式树

这有解决方法吗?

2 个答案:

答案 0 :(得分:3)

所以我们在这里可以做的是创建一个Compose方法,它使用Expression表示带有一个参数和返回值的lambda,然后是第二个带有第一个lambda输出的lambda,因为它是输入,然后返回一个新的lambda,它接受第一个参数的输入并返回第二个参数的输出。

如果它只是常规代理,那么该方法看起来就像这样(只是为了让你知道它在概念上做了什么):

public static Func<TFirst, TResult> Compose<TFirst, TIntermediate, TResult>(
    Func<TFirst, TIntermediate> first,
    Func<TIntermediate, TResult> second)
{
    return firstParam => second(first(firstParam));
}

这在Expression个对象中实现,更复杂:

public static Expression<Func<TFirstParam, TResult>>
    Compose<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

这使用以下方法将一个Expression的所有实例替换为另一个:

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

现在我们有了Compose方法,我们可以编写一个表达式,它接受你的项目并返回Contract,然后编写另一个接受该合同并计算bool的方法}表明它是否有效:

public static Expression<Func<Item, bool>> GetFilter(
    TimeSpan fromValue, TimeSpan toValue)
{
    Expression<Func<Item, Contract>> currentContract =
        q => q.StaffContracts
                .OrderByDescending(p => p.SignedDate)
                .Where(p => p.Active)
                .FirstOrDefault();

    return currentContract.Compose(contract =>
        contract != null &&
        contract.TimeSpan >= fromValue &&
        contract.TimeSpan <= toValue);
}

此处的Compose方法将在内部用contract的正文替换第二个lambda中currentContract的所有实例。所以效果就是如果你把它写了三次,即使这可以防止你需要这样做。

这种Compose方法可以随时用于在表达式树中创建变量(查询提供程序不支持的内容)。您始终可以创建一个计算变量值的方法,然后Compose在另一个表达式中使用它。

答案 1 :(得分:0)

我担心您必须使用System.Linq.Expressions.Expression类方法手动准备表达式:

public static Expression<Func<Item, bool>> GetWhere(TimeSpan fromValue, TimeSpan toValue)
{
    Expression<Func<Item, Contract>> currentContract =
        q => q.StaffContracts
              .OrderByDescending(p => p.SignedDate)
              .Where(p => p.Active)
              .FirstOrDefault();

    var param = currentContract.Parameters.First();

    return Expression.Lambda<Func<Item, bool>>(
                Expression.And(
                    Expression.And(
                        Expression.NotEqual(
                            currentContract,
                            Expression.Constant(null, typeof(Contract))),
                        Expression.GreaterThanOrEqual(
                            Expression.Property(currentContract, "Timespan"),
                            Expression.Constant(fromValue))),
                    Expression.LessThanOrEqual(
                        Expression.Property(currentContract, "Timespan"),
                        Expression.Constant(toValue))),
                param);
}