ExpressionTree 重写 - 参数“x”;不在范围内
如果我在以下代码中犯了任何错误/错误,请不要生气,只需在此处发表评论,我会立即修复 - 谢谢
目标
重新映射 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
,它知道 EntityA
和 EntityB
是什么,并且对 Assembly1
可见。 Assembly2
为 Assembly1
提供了一种方法,可能如下所示:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
在查看乔恩的非常相似的问题 并重构以纳入他的一些实践,我更喜欢我自己的实现,我偶然发现了答案。我注意到
VisitParameter
从未被调用,其原因是我对VisitMemberAccess
的重写停止了通过表达式树的递归。它应该看起来像(使用不同的重载):
将其与确保不会创建同一参数的多个实例并且所有内容都很好地组合在一起。
再次感谢乔恩! =)
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 ofVisitMemberAccess
discontinued the recursion through the expression tree.It should have looked like (using a different overload):
Combine that with ensuring you don't create multiple instances of the same parameter and everything slots together nicely.
Thanks again to Jon! =)