开发者

ExpressionTree rewrite - The parameter 'x' is not in scope

开发者 https://www.devze.com 2023-04-03 18:05 出处:网络
If I make any mistakes/mistypes in the following code, please don\'t get irrate, just drop a comment on here and I\'ll fix immediately - thanks

If I make any mistakes/mistypes in the following code, please don't get irrate, just drop a comment on here and I'll fix immediately - thanks

Goal

Re-map Expression<TDelegate> from one EntityA to a EntityB.

I suspect this kind of thing has been done before but I haven't found any particularly useful links so feel free to point me in the right direction.

What I have so far is a selection of classes that combine to allow for mappings to be created between Entity Members on two given classes. As an example, the surface API might have the following signature:

public void AddMemberBinding<TEntityA, TEntityB, TMember>(Func<TEntityA, TMember> entityAMemberSelector, Func<TEntityB, TMember> entityBMemberSelector)
{
    // does some magic, eventually storing the necessary MemberInfo details required to
    // "remap" MemberExpressions (MemberAccess) from TEntityA to TEntityB
}

Given the following classes...

public class EntityA
{
    public long Id { get; set; }
    public string Name { get; set ;}
}
public class EntityB
{
    public long MyId { get; set; }
    public string MyName { get; set; }
}

You would be able to create bindings with something along the lines of...

public static void AddBindings()
{
    AddMemberBinding((EntityA n) => n.Id, (EntityB n) => n.MyId);
    AddMemberBinding((EntityA n) => n.Name, (EntityB n) => n.MyName);
}

In my case, I have Assembly1 that knows what EntityA is, but does not know EntityB. I have Assembly2 which knows what both EntityA and EntityB are, and is visible to Assembly1. Assembly2 provides a method to Assembly1 which might look as follows:

public static IEnumerable<EntityA> GetData<TResult>(Expression<Func<EntityA, bool>> criteria, Expression<Func<EntityA, TResult>> selector)
{
    // EntityB's are stored in a database, I could do one of two things here...
    // 1) Return all EntitieB's and then apply criteria and selector through the IEnumerable extensions
    //    this would be sub-optimal - particularly if there are millions of EntityB's!
    // 2) "Transmute" (for lack of a better word) the expressions provided, using the keymappings
    //    specified earlier, to derive expressions that can be passed through to the QueryableProvider
    // ... as you might have guessed, I opted for #2
}

I'm using a derived version of the ExpressionTree Visitor, with the following overridden methods:

prote开发者_如何学Ccted override Expression VisitLambda(LambdaExpression lambda)
{
    Type targetParameterType = lambda.Parameters[0].Type;
    Type targetExpressionType = lambda.Type;
    If (lambda.Parameters.Count = 1 && lambda.Parameters(0).Type == EntityA)
    {
        targetParameterType = EntityB;
        // the `GetResultType` method called gets the TResult type from Func<T, TResult>
        Type targetExpressionResultType = GetResultType(lambda);
        targetExpressionType = gettype(Func<EntityB, targetExpressionResultType>) 
    }
    // this is probably wrong, but maintains the current (last) parameter instance
    // I started doing this after reading about a similar issue to mine found:
    // https://stackoverflow.com/questions/411738/expression-or-the-parameter-item-is-not-in-scope
    this.CurrentLambdaParameters = lambda.Parameters.Select(x => Expression.Parameter(targetParameterType, x.Name));
    Expression body = this.Visit(lambda.Body);
    If (body != lambda.Body)
    {
        return Expression.Lambda(targetExpressionType, body, this.CurrentLambdaParameters);
    }
    return lambda;
}

protected override Expression VisitMemberAccess(MemberExpression m)
{
    // at this point I go off and look at key mappings, fetch the mapping required, etc
    // the entity I retrieve has a `TargetMemberInfo` property which is a `MemberInfo` for the
    // member on the target entity - speaks for itself I guess...
    return Expression.MakeMemberAccess(this.CurrentParameters.Single(), myMappingClassThing.TargetMemberInfo);
}

The Problem

With all that said and done, when I run through the code with a test case, I get the error in the title... I can see it's a parameter issue from the description, but having read about a similiar issue I had hoped I had accounted for the problem in the VisitMemberAccess method by using the parameter I created when modifying the root lambda expression - same instance of ParameterExpression fixed the linked questions problem I think?

It seems I didn't understand that part of the process very well. The question is, where did I go wrong!? What is it I need to do with these ParameterExpressions for them to be ""in scope""?

Thanks in advance for your answers, and if you read this far, kudos to you!!


Whilst looking over Jon's remarkably similiar question and refactoring to incorporate a couple of his practices I preferred to my own implementation, I stumbled across the answer. I noticed that VisitParameter was never called, the reason for which is that my override of VisitMemberAccess discontinued the recursion through the expression tree.

It should have looked like (using a different overload):

protected override Expression VisitMemberAccess(MemberExpression m) 
{ 
    return Expression.MakeMemberAccess(Visit(m.Expression), myMappingClassThing.TargetMemberInfo); 
} 

Combine that with ensuring you don't create multiple instances of the same parameter and everything slots together nicely.

Thanks again to Jon! =)

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号