I want to be able to used nested extension methods to do projection of entities in EF to corresponding view models. (see my previous question Projection of single entities in EF with extension methods for more details on what im doing).
As per this question I built an attribute to replace an extension method in an expression tree with a lambda to be able to do this. It takes the method arguments from the extentsion method and replaces them on as VisitParameter is called (I dont know if there is a way to replace the parameters inline in the LambdaExpression).
This works well for something like this:
entity => new ProfileModel
{
Name = entity.Name
}
And I can see the expression visitor replace the entity parameter on the LambdaExpression to the correct one from the extension method args.
However when I change it to something more nested say,
entity => new ProfileModel
{
SomethingElses = entity.SomethingElses.AsQueryable().ToViewModels()
}
then I get:
The parameter 'entity' was not bound in the specified LINQ to Entities query expression.
Additionally VisitParameter
in my expression visitor doesn't seem to get called at all with the parameter 'entity'.
Its like its not using my visitor at all for the second Lambda, but I dont know why It would for one and not the other?
How can I correctly replace the parameter in the case of both types of lambda expressions?
My Visitor below:
protected override Expression VisitMethodCall(MethodCallExpression node)
{
bool expandNode = node.Method.GetCustomAttributes(typeof(ExpandableMethodAttribute), false).Any();
if (expandNode && node.Method.IsStatic)
{
object[] args = new object[node.Arguments.Count];
args[0] = _provider.CreateQuery(node.Arguments[0]);
for (int i = 1; i < node.Arguments.Count; i++)
{
Expression arg = node.Arguments[i];
args[i] = (arg.NodeType == ExpressionType.Constant) ? ((ConstantExpression)arg).Value : arg;
}
return ((IQueryable)node.Method.Invoke(null, args)).Expression;
}
var replaceNodeAttributes = node.Method.GetCustomAttributes(typeof(ReplaceInExpressionTree), false).Cast<ReplaceInExpressionTree>();
if (replaceNodeAttributes.Any() && node.Method.IsStatic)
{
var replaceWith = node.Method.DeclaringType.GetMethod(replaceNodeAttributes.First().MethodName).Invoke(null, null);
if (replaceWith is LambdaExpression)
{
RegisterReplacementParameters(node.Arguments.ToArray(), replaceWith as LambdaExpression);
return Visit((replaceWith as LambdaExpression).Body);
}
}
return base.VisitMethodCall(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
Expression replacement;
if (_replacements.TryGetValue(node, out replacement))
return Visit(replacement);
return base.VisitParameter(node);
}
private void RegisterReplacementParameters(Expression[] parameterValues, LambdaExpression expressionToVisit)
{
if (parameterValues.Length != expressionToVisit.Parameters.Count)
throw new ArgumentException(string.Format("The parameter values count ({0}) does not match the expression parameter count ({1})", parameterValues.Length, expressionToVisit.Parameters.Count));
foreach (var x in expressionToVisit.Parameters.Select((p, idx) => new { Index = idx, Parameter = p }))
{
if (_replacements.ContainsKey(x.Parameter))
{
throw new Exception("Parameter already registered, this shouldn't happen.");
}
_replacements.Add(x.Parameter, parameterValues[x.Index]);
}
}
Full repro code example here: https://github.com/lukemcgregor/ExtensionMethodProjection
Edit:
I now have a blog post (Composable Repositories - Nesting Extensions) and nuget package to help with nesting extension methods in linq