I've been trying to factor some common lambda subexpressions out into reusable components and have hit a wall. I'll show what I've done so far with a simplified example and hope one of you can shed some light.
My subexpressions are ultimately used in an NHibernate Query (IQueryable interface). Here's an example:
var depts = session.Query<Department>().Where(d => d.employees.Any(ex1.AndAlso(ex2).Compile()));
AndAlso is an Expression
extension that's defined like this (taken from an answer to a related SO question, with some minor adjustments):
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);
}
}
All would be well except that the Any call is an IEnumerable
call, not an IQueryable
call, so it expects a Func
argument rather than an Expression
. To that end I call Compile()
on the combined Expression
, but then I get the following run-time error:
Remotion.Linq.Parsing.ParserException: Could not parse expression 'c.employees.Any(value(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 arguments, MethodCallExpression expressionToParse) at Remotion.Linq.Parsing.Structure.ExpressionTreeParser.ParseMethodCallExpression(MethodCallExpression methodCallExpression, String associatedIdentifier) at Remotion.Linq.Parsing.Structure.QueryParser.GetParsedQuery(Expression expressionTreeRoot) at Remotion.Linq.Parsing.ExpressionTreeVisitors.SubQueryFindingExpressionTreeVisitor.VisitExpression(Expression expression) at Remotion.Linq.Parsing.ExpressionTreeVisitor.VisitLambdaExpression(LambdaExpression expression) at Remotion.Linq.Parsing.Structure.MethodCallExpressionParser.ProcessArgumentExpression(Expression argumentExpression) at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext()
1..ctor(IEnumerable
at System.Linq.Buffer1 source) at System.Linq.Enumerable.ToArray(IEnumerable
1 source) at Remotion.Linq.Parsing.Structure.MethodCallExpressionParser.Parse(String associatedIdentifier, IExpressionNode source, IEnumerable`1 arguments, MethodCallExpression expressionToParse) at Remotion.Linq.Parsing.Structure.ExpressionTreeParser.ParseMethodCallExpression(MethodCallExpression methodCallExpression, String associatedIdentifier) at Remotion.Linq.Parsing.Structure.QueryParser.GetParsedQuery(Expression expressionTreeRoot) at NHibernate.Linq.NhRelinqQueryParser.Parse(Expression expression) in NhRelinqQueryParser.cs: line 39
...and so on for the rest of the stack.
My conundrum seems to be that one can't combine Funcs
, only Expressions
-- which produces another Expression
. But one can't hand an Expression
to IEnumerable.Any()
-- only a Func
. But then the Func
produced by Expression.Compile()
seems to be the wrong kind...
Any ideas?
Michael
Consider this code:
With this code,
func
will be a reference to compiled code, whileexpression
will be a reference to an abstract syntax tree representing the lambda expression. CallingCompile()
on the expression will return compiled code functionally equivalent tofunc
.What type of expression is contained in the compiled code is irrelevant - the LINQ providers simply cannot decode compiled code. They rely on walking the abstract syntax tree represented by the
Expression<Func<...>>
type.In your case, you apply
Any()
ond.employees
. Sinceemployees
is anIEnumerable<T>
, you will get the version ofAny()
that expects to get compiled code that it can run. Note thatAny()
is also available for queryables, and will in that case accept an expression.You could try
AsQueryable()
, but I'm not sure it will work:Otherwise, you have to rewrite it without using any.