在上一个问题(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表达式无法转换为表达式树
这有解决方法吗?
答案 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);
}