我有以下类(class):
class Source {
public int Id { get; set; }
public string Name { get; set; }
public SourceItem Item { get; set; }
}
class SourceItem {
public Guid Id { get; set; }
public decimal Price { get; set; }
}
class Dest {
public int Id { get; set; }
public string Name { get; set; }
public DestItem Item { get; set; }
}
class DestItem{
public Guid Id { get; set; }
public decimal Price { get; set; }
}
我构建了以下 lambda 表达式:
Expression<Func<Source, Dest>> projectionSource = source => new Dest{
Id = source.Id,
Name = source.Name
};
Expression<Func<SourceItem, DestItem>> projectionItem = item => new DestItem
{
Id = item.Id,
Price = item.Price
};
Expression<Func<Source, SourceItem>> sourceMember = source => source.Item;
Expression<Func<Dest, DestItem>> destMember = dest => dest.Item;
如何使用projectionItem lambda 表达式中的memberInitExpression 表达式将新元素绑定(bind)添加到原始投影表达式的元素绑定(bind)列表中?输出处需要 Lambda:
source => new Dest() // part of projectionSource expression
{
Id = source.Id,
Name = source.Name,
Item = new DestItem() // part of projectionItem expression
{
Id = source.Item.Id,
Price = source.Item.Price
}
}
我无法从 lambda destMember 和projectionItem 创建MemberAccess 并将其添加到绑定(bind)列表projectionSource。
最佳答案
您无需将成员添加到绑定(bind)列表,而是构建一个新表达式。
使用以下帮助程序来替换参数(如果您引用了 EF Core 3+,则可以使用它的 ReplacingExpressionVisitor
来代替):
public static class ExpressionExt
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacingVisitor { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacingVisitor : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}
您可以执行以下操作:
// get the body
var projectionSourceBody = projectionSource.Body as MemberInitExpression;
// get the "nested" Item members to map:
var sourceMemberE = sourceMember.Body as MemberExpression;
var destMemberE = destMember.Body as MemberExpression;
// replace the "nested" projection expression parameter with source.Item
var projectItemE = projectionItem.Body.ReplaceParameter(
projectionItem.Parameters[0],
Expression.MakeMemberAccess(projectionSource.Parameters[0], sourceMemberE.Member));
// generate the Item = new DestItem {...} member binding for init expression
var assignItem = Expression.Bind(destMemberE.Member, projectItemE);
// rebuild member init expression with new binding list
// using C# 12 collection expressions here
// can use projectionSourceBody.Bindings.Append(assignItem) instead
var newInitE = Expression.MemberInit(
projectionSourceBody.NewExpression,
[.. projectionSourceBody.Bindings, assignItem]);
// create new lambda
var newProjectionSource = Expression.Lambda<Func<Source, Dest>>(newInitE, projectionSource.Parameters);
newProjectionSource.Compile(); // compile to at least verify we did something compilable
请注意,可能值得研究现有的映射器,它们支持开箱即用的此类功能,因此您无需重新发明轮子。例如,AutoMapper“理解”“嵌套”映射并支持构建供 LINQ 提供程序使用的表达式 - 请参阅 Queryable Extensions doc(搜索 ProjectTo
和 CreateProjection
示例)。
关于c# - 如何将 MemberInitExpression 添加到绑定(bind)其他 Lambda MemberInitExpression,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77828659/