ExpressionTree 重写 - 参数“x”;不在范围内

发布于 2024-12-03 05:56:50 字数 4307 浏览 0 评论 0原文

如果我在以下代码中犯了任何错误/错误,请不要生气,只需在此处发表评论,我会立即修复 - 谢谢

目标

重新映射 Expression;从一个EntityA到一个EntityB

我怀疑这种事情以前已经做过,但我还没有找到任何特别有用的链接,所以请随时为我指出正确的方向。

到目前为止,我所拥有的是一系列类的选择,这些类组合起来允许在两个给定类的实体成员之间创建映射。例如,表面 API 可能具有以下签名:

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
}

给定以下类...

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; }
}

您将能够使用以下内容创建绑定...

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

在我的例子中,我有 Assembly1它知道EntityA是什么,但不知道EntityB。我有 Assembly2,它知道 EntityAEntityB 是什么,并且对 Assembly1 可见。 Assembly2Assembly1 提供了一种方法,可能如下所示:

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
}

我正在使用 ExpressionTree Visitor,具有以下重写方法:

protected 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);
}

问题

上述所有 和完成后,当我使用测试用例运行代码时,我在标题中收到错误...我可以从描述中看到这是一个参数问题,但已经阅读了 类似问题 我曾希望我已经在 VisitMemberAccess 中解决了该问题使用参数 I 的方法修改根 lambda 表达式时创建 - ParameterExpression 的同一实例修复了我认为的链接问题问题?

看来我对这部分过程不太了解。那么问题来了,我到底哪里做错了!?我需要对这些参数表达式做什么才能使它们“在范围内”?

预先感谢您的回答,如果您读到这里,向您致敬!

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:

protected 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!!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

谢绝鈎搭 2024-12-10 05:56:50

在查看乔恩的非常相似的问题 并重构以纳入他的一些实践,我更喜欢我自己的实现,我偶然发现了答案。我注意到 VisitParameter 从未被调用,其原因是我对 VisitMemberAccess 的重写停止了通过表达式树的递归。

它应该看起来像(使用不同的重载):

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

将其与确保不会创建同一参数的多个实例并且所有内容都很好地组合在一起。

再次感谢乔恩! =)

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! =)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文