将Linq表达式组合成函数时出现问题



我一直在尝试将一些常见的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.WhereSelectEnumerableIterator 2.MoveNext()
at System.Linq.Buffer
..ctor(IEnumerable 1 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()。由于employeesIEnumerable<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()。员工,这很有效!

相关内容

  • 没有找到相关文章

最新更新