将Linq表达式组合成Func

时间:2015-06-09 10:38:39

标签: linq nhibernate ienumerable iqueryable

我一直试图将一些常见的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.WhereSelectEnumerableIterator 2.MoveNext()
at System.Linq.Buffer
1..ctor(IEnumerable 1 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()似乎是错误的......

有什么想法吗?

迈克尔

2 个答案:

答案 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。由于employeesIEnumerable<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(),这很有效!