c# - 如何将 MemberInitExpression 添加到绑定(bind)其他 Lambda MemberInitExpression

标签 c# linq expression linq-to-entities linq-expressions

我有以下类(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(搜索 ProjectToCreateProjection 示例)。

关于c# - 如何将 MemberInitExpression 添加到绑定(bind)其他 Lambda MemberInitExpression,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77828659/

相关文章:

c# - 如何使用 C# 从 Azure 容器/Blob 读取/下载所有文件?

c# - SQLite ExecuteReader --> DataTable.Load --> FormatException (DateTime)

c++ - 是否可以在 C++ 中的表达式中定义变量?

java - log4.xml 中用于访问 java 系统属性的表达式语言是什么?

c# - 组合相同输入类型的选择表达式

c# - 字符串未被识别为有效的日期时间

java - 多线程套接字服务器 - 客户端相互通信

c# - LINQ 表达式失败,但 foreach 循环有效 Entity-Framework

php - 什么是 LINQ?我如何在 PHP 中使用它?

c# - Linq - 从另一个列表中删除一个匿名列表