用于将 MemberInitExpression 分配给属性的表达式树方法
本文关键字:表达式 方法 属性 MemberInitExpression 分配 用于 | 更新日期: 2023-09-27 18:36:31
我正在尝试使用表达式树,以便我可以选择使用实体框架映射到 DTO,其方式与 Include 指令在 DbSet 上工作的方式大致相同(实现 OData 的开放 sorce 项目的一部分)。
下面的代码表示一个测试用例。
Expression<Func<Bar, Bar>> mapBar = b => new Bar { BarInt = b.BarInt, BarString = b.BarString };
Expression<Func<Foo, Foo>> mapFoo = f => new Foo { FooInt = f.FooInt, B = null };
Expression<Func<Foo, Foo>> target = f => new Foo { FooInt = f.FooInt,
B = new Bar {
BarInt=f.B.BarInt, BarString = f.B.BarString
} };
注意 Foo.B 为空,并插入了映射栏表达式。
我已经使用以下表达式访问者添加导航属性
public class UpdateExpressionVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldExpr;
private readonly Expression _newExpr;
public UpdateExpressionVisitor(ParameterExpression oldExpr, Expression newExpr)
{
_oldExpr = oldExpr;
_newExpr = newExpr;
}
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
if (node.Member.Name == _oldExpr.Name)
{
return node.Update(_newExpr);
}
Console.WriteLine(node);
return base.VisitMemberAssignment(node);
}
}
但是我不知道如何改变表达new Bar { BarInt = b.BarInt,...
,变得new Bar { BarInt = f.B.BarInt,...
该函数将需要某种像这样的 ExpressionVisitor
public class MergingVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldExpr;
private readonly ParameterExpression _newProp;
private readonly ParameterExpression _newParent;
public MergingVisitor(ParameterExpression oldExpr, ParameterExpression newProp, ParameterExpression newParent)
{
_oldExpr = oldExpr;
_newProp = newProp;
_newParent = newParent;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == _oldExpr)
{
/*!!what to do here!!*/
var ma = Expression.MakeMemberAccess(_newProp, node.Member);
return Expression.MakeMemberAccess(_newParent, ma.Member);
}
return base.VisitMember(node);
}
它们都需要通过类似的东西链接在一起
public static Expression<Func<T, TMap>> MapNavProperty<T, TMap, U, UMap>(this Expression<Func<T, TMap>> parent, Expression<Func<U, UMap>> nav, string propName)
{
//concern 1 remap name of prop in nav - not sure if I should do this first
var parentInitVarName = parent.Parameters[0].Name;
var parentParam = Expression.Parameter(typeof(T), parentInitVarName);
var propParam = Expression.Parameter(typeof(U), propName);
var mergeVisitor = new MergingVisitor(nav.Parameters[0], propParam, parentParam);
var newNavBody = mergeVisitor.Visit(nav.Body); //as MemberExpression;
//concern 2 replace given property
var visitor = new UpdateExpressionVisitor(propParam, nav.Body);
return (Expression<Func<T, TMap>>)visitor.Visit(parent);
}
尽管目前 parentParam 在 Expression.MakeMemberAccess
函数中会失败,因为它的类型是 T(上面示例中的 Foo),而不是类型 U。
如何更改子属性的 lamda 表达式中的变量名称和类型 - 谢谢。
更新
Svick 和 Ivan Stoev 的答案在解释上很博学,并且都完美地工作 - 两个答案的(通过)单元测试和代码都在 GitHub 上。非常感谢你们俩 - 很遗憾我不能勾选 2 个答案,所以归结为 Eeny、meeny、miny、moe。
var parentParam = Expression.Parameter(typeof(T), parentInitVarName);
这不起作用,表达式中的参数不是由名称标识的,而是按引用标识的。您需要做的只是获取父级的参数。
var propParam = Expression.Parameter(typeof(U), propName);
这对我来说没有任何意义。 propName
是属性的名称,因此需要使用它来创建成员访问表达式,而不是参数表达式。
public MergingVisitor(ParameterExpression oldExpr, ParameterExpression newProp, ParameterExpression newParent)
你需要的是用另一个表达式(f.B
)替换一个表达式(b
)。为此,我会创建:
public class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression _oldExpr;
private readonly Expression _newExpr;
public ReplaceVisitor(Expression oldExpr, Expression newExpr)
{
_oldExpr = oldExpr;
_newExpr = newExpr;
}
public override Expression Visit(Expression node)
{
if (node == _oldExpr)
{
return _newExpr;
}
return base.Visit(node);
}
}
public UpdateExpressionVisitor(ParameterExpression oldExpr, Expression newExpr)
oldExpr
成为参数表达式没有任何意义。你需要的只是一个string
.
var visitor = new UpdateExpressionVisitor(propParam, nav.Body);
您需要在此处实际使用newNavBody
。
整个MapNavProperty()
现在将如下所示:
var parentParam = parent.Parameters.Single();
var propExpression = Expression.Property(parentParam, propName);
var mergeVisitor = new ReplaceVisitor(nav.Parameters.Single(), propExpression);
var newNavBody = mergeVisitor.Visit(nav.Body);
var visitor = new UpdateExpressionVisitor(propName, newNavBody);
return (Expression<Func<T, TMap>>)visitor.Visit(parent);
通过这些更改,您的代码将正常工作。
我个人会使用不同的助手:
(1) 用于替换参数
static Expression ReplaceParameter(this LambdaExpression lambda, Expression target)
{
return lambda.Body.ReplaceParameter(lambda.Parameters.Single(), target);
}
static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
(2) 用于替换成员赋值
static Expression ReplaceMemberAssignment(this Expression expression, MemberInfo member, Expression value)
{
return new MemberAssignmentReplacer { Member = member, Value = value }.Visit(expression);
}
class MemberAssignmentReplacer : ExpressionVisitor
{
public MemberInfo Member;
public Expression Value;
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
{
return node.Member == Member ? node.Update(Value) : base.VisitMemberAssignment(node);
}
}
使用这些帮助程序,所讨论的函数将是这样的:
public static Expression<Func<T, TMap>> MapNavProperty<T, TMap, U, UMap>(this Expression<Func<T, TMap>> parent, Expression<Func<U, UMap>> nav, string propName)
{
var parameter = parent.Parameters[0];
var body = parent.Body.ReplaceMemberAssignment(
typeof(TMap).GetProperty(propName),
nav.ReplaceParameter(Expression.Property(parameter, propName))
);
return Expression.Lambda<Func<T, TMap>>(body, parameter);
}