我一直试图将一些常见的lambda子表达式分解为可重用的组件并且已经碰壁了。我将用一个简单的例子展示我到目前为止所做的事情,并希望你们中的一个人可以解释一下。
我的子表达式最终用于NHibernate查询(IQueryable接口)。这是一个例子:
var depts = session.Query<Department>().Where(d => d.employees.Any(ex1.AndAlso(ex2).Compile()));
AndAlso 是一个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);
}
}
除非任意调用是IEnumerable
调用,而不是IQueryable
调用,否则一切都会很好,所以它需要一个Func
参数而不是Expression
。为此,我在合并的Compile()
上调用Expression
,但之后出现以下运行时错误:
Remotion.Linq.Parsing.ParserException:无法解析表达式 “c.employees.Any(值(System.Func
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个参数,MethodCallExpression expressionToParse) 在 Remotion.Linq.Parsing.Structure.ExpressionTreeParser.ParseMethodCallExpression(MethodCallExpression methodCallExpression,字符串associatedIdentifier) 在 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()
1..ctor(IEnumerable
at System.Linq.Buffer1 source) at System.Linq.Enumerable.ToArray(IEnumerable
1 source)at at Remotion.Linq.Parsing.Structure.MethodCallExpressionParser.Parse(字符串associatedIdentifier,IExpressionNode源,IEnumerable`1参数,MethodCallExpression expressionToParse) 在 Remotion.Linq.Parsing.Structure.ExpressionTreeParser.ParseMethodCallExpression(MethodCallExpression methodCallExpression,字符串associatedIdentifier) 在 Remotion.Linq.Parsing.Structure.QueryParser.GetParsedQuery(表达式expressionTreeRoot) 在NHibernate.Linq.NhRelinqQueryParser.Parse(表达式表达式)中 NhRelinqQueryParser.cs:第39行
......依旧等待堆栈的其余部分。
我的难题似乎是一个人无法合并Funcs
,只能合并Expressions
- 这会产生另一个Expression
。但是,无法将Expression
传递给IEnumerable.Any()
- 只能传递Func
。但是Func
产生的Expression.Compile()
似乎是错误的......
有什么想法吗?
迈克尔
答案 0 :(得分:4)
考虑以下代码:
Func<T1, TResult> func = t => t == 5;
Expression<Func<T1, TResult>> expression = t => t == 5;
使用此代码,func
将是对已编译代码的引用,而expression
将是对表示lambda表达式的抽象语法树的引用。在表达式上调用Compile()
将返回在功能上等同于func
的编译代码。
编译代码中包含哪种类型的表达式是无关紧要的 - LINQ提供程序根本无法解码已编译的代码。它们依赖于遍历由Expression<Func<...>>
类型表示的抽象语法树。
在您的情况下,您在Any()
上应用d.employees
。由于employees
是IEnumerable<T>
,因此您将获得期望获得可以运行的已编译代码的Any()
版本。请注意,Any()
也可用于可查询,并且在这种情况下将接受表达式。
您可以尝试AsQueryable()
,但我不确定它是否有效:
session.Query<Department>()
.Where(d => d.employees.AsQueryable().Any(ex1.AndAlso(ex2)));
否则,您必须在不使用任何内容的情况下重写它。
答案 1 :(得分:0)
我想到,鉴于问题围绕IEnumerable
方法采用Func
个参数的事实,如果我能得到 d.employees (在我的例子中) )要成为IQueryable
而不是IEnumerable
,那么其Any
将会Expression
,我就会完成。
所以,我尝试在 d.employees 之后插入一个.AsQueryable()
,这很有效!