我一直在尝试将一些常见的lambda子表达式分解为可重用的组件,但是遇到了瓶颈。我将用一个简化的例子来展示我到目前为止所做的工作,希望你们中的一个人能有所启发。
我的子表达式最终在NHibernate查询(IQueryable接口)中使用。下面是一个例子:
var depts = session.Query<Department>().Where(d => d.employees.Any(ex1.AndAlso(ex2).Compile()));
and 是一个Expression
扩展,它是这样定义的(取自对相关SO问题的回答,进行了一些小的调整):
public class ParameterRebinder : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> _map;
public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
_map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p)
{
ParameterExpression replacement;
if (_map.TryGetValue(p, out replacement))
{
p = replacement;
}
return base.VisitParameter(p);
}
}
public static class ExpressionExtensions
{
public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
{
// build parameter map (from parameters of second to parameters of first)
var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);
// replace parameters in the second lambda expression with parameters from the first
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// apply composition of lambda expression bodies to parameters from the first expression
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.AndAlso);
}
public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.OrElse);
}
}
一切都很好,除了Any调用是IEnumerable
调用,而不是IQueryable
调用,所以它期望Func
参数而不是Expression
。为此,我在合并的Expression
上调用Compile()
,但随后我得到以下运行时错误:
Remotion.Linq.Parsing。ParserException:无法解析表达式"c.employees.Any(值(系统。函数
2[Entities.Domain.Department,System.Boolean]))': Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'. If you tried to pass a delegate instead of a LambdaExpression, this is not supported because delegates are not parsable expressions. at Remotion.Linq.Parsing.Structure.MethodCallExpressionParser.CreateExpressionNode(Type nodeType, MethodCallExpressionParseInfo parseInfo, Object[] additionalConstructorParameters) at Remotion.Linq.Parsing.Structure.MethodCallExpressionParser.Parse(String associatedIdentifier, IExpressionNode source, IEnumerable
有1个参数在Remotion.Linq.Parsing.Structure.ExpressionTreeParser。ParseMethodCallExpression(方法callexpression方法callexpression, String associateidentifier)在Remotion.Linq.Parsing.Structure.QueryParser。GetParsedQuery(表达式expressionTreeRoot)在Remotion.Linq.Parsing.ExpressionTreeVisitors.SubQueryFindingExpressionTreeVisitor。VisitExpression表达式(表达式)在Remotion.Linq.Parsing.ExpressionTreeVisitor。VisitLambdaExpression (LambdaExpression表达式)在Remotion.Linq.Parsing.Structure.MethodCallExpressionParser。ProcessArgumentExpression(表达式argumentExpression)在System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext()
..ctor(IEnumerable
at System.Linq.Buffer1 source) at System.Linq.Enumerable.ToArray(IEnumerable
1源Remotion.Linq.Parsing.Structure.MethodCallExpressionParser。解析(String associatedIdentifier, IExpressionNode源,IEnumerable ' 1个参数,MethodCallExpression expressionparse)在Remotion.Linq.Parsing.Structure.ExpressionTreeParser。ParseMethodCallExpression(方法callexpression方法callexpression, String associateidentifier)在Remotion.Linq.Parsing.Structure.QueryParser。GetParsedQuery(表达式expressionTreeRoot)在NHibernate.Linq.NhRelinqQueryParser。解析(表达式表达式)NhRelinqQueryParser.cs: line 39
…
我的难题似乎是一个人不能组合Funcs
,只有Expressions
——这产生了另一个Expression
。但是不能把Expression
交给IEnumerable.Any()
,只能交给Func
。但是,Expression.Compile()
产生的Func
似乎是错误的类型…
任何想法?
迈克尔考虑以下代码:
Func<T1, TResult> func = t => t == 5;
Expression<Func<T1, TResult>> expression = t => t == 5;
对于这段代码,func
将是对编译代码的引用,而expression
将是对表示lambda表达式的抽象语法树的引用。在表达式上调用Compile()
将返回与func
功能等效的编译后代码。
编译后的代码中包含什么类型的表达式是无关紧要的——LINQ提供程序根本无法解码编译后的代码。它们依赖于遍历由Expression<Func<...>>
类型表示的抽象语法树。
在您的例子中,您在d.employees
上应用Any()
。由于employees
是IEnumerable<T>
,因此您将获得Any()
的版本,该版本期望获得可以运行的编译代码。请注意,Any()
也可用于查询项,在这种情况下将接受表达式。
你可以尝试AsQueryable()
,但我不确定它会工作:
session.Query<Department>()
.Where(d => d.employees.AsQueryable().Any(ex1.AndAlso(ex2)));
我想到,考虑到问题围绕着IEnumerable
方法接受Func
参数的事实,如果我能得到d。员工(在我的例子中)是IQueryable
而不是IEnumerable
,那么它的Any
将采用Expression
,我就完成了。
所以,我试着在d之后插入.AsQueryable()
。员工,这很有效!