You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Automapper:避免重复投影逻辑且不修改DestinationDto结构的方法

问题

我希望在Automapper映射中避免重复编写src.Statuses.Where(s => s.StatusType!.StatusGroupId == 1).OrderByDescending(s => s.CreatedUTC).First()这段代码,同时不修改DestinationDto的结构。我知道可以通过给DestinationDto新增StatusDto对象并定义独立投影来实现,但想了解Automapper是否有无需创建额外Dto的语法——即先获取指定的Status对象,再在多个.ForCtorParam中复用它。

源实体关系:

Source     1 -- 0..N Status
Status     0..N -- 1 StatusType
StatusType 0..N -- 1 StatusGroup

现有映射代码:

public class DestinationDto
{
    public DestinationDto(...)

    public int Id { get; set; }
    public DateTime StatusDate { get; set; }
    public string Open { get; set; }
    public string Status { get; set; }
    public DateTime Created { get; set; }
}

public class DestinationProfile : Profile
{
    public DestinationProfile()
    {
        CreateProjection<SourceType, DestinationDto>(MemberList.Destination)
            .ForCtorParam(nameof(DestinationDto.Id), opt => opt.MapFrom(src => src.Id))
            .ForCtorParam(nameof(DestinationDto.StatusDate), 
                    opt => opt.MapFrom(src => src.Statuses.Where(s => s.StatusType!.StatusGroupId == 1).OrderByDescending(s => s.CreatedUTC).First().CreatedUTC))
            .ForCtorParam(nameof(DestinationDto.Open), 
                    opt => opt.MapFrom(src => src.Statuses.Where(s => s.StatusType!.StatusGroupId == 1).OrderByDescending(s => s.CreatedUTC).First().StatusType!.IsOpen))
            .ForCtorParam(nameof(DestinationDto.Status),
                    opt => opt.MapFrom(src => src.Statuses.Where(s => s.StatusType!.StatusGroupId == 1).OrderByDescending(s => s.CreatedUTC).First().StatusType!.Label))
            .ForCtorParam(nameof(DestinationDto.Created), opt => opt.MapFrom(src => src.CreatedUTC));

    }
}
解决方案

因为你使用的是CreateProjection(用于LINQ查询的表达式树投影,兼容EF等ORM),所以必须用表达式树兼容的方式来复用逻辑,以下是两种可行方案:

方案1:定义复用的表达式树变量

在Profile构造函数中提前定义获取目标Status的表达式,然后在每个ForCtorParam中通过Invoke引用该表达式,避免重复编写查询逻辑:

public class DestinationProfile : Profile
{
    public DestinationProfile()
    {
        // 定义复用的表达式:获取目标Status对象
        Expression<Func<SourceType, Status>> getTargetStatus = src => 
            src.Statuses.Where(s => s.StatusType!.StatusGroupId == 1)
                        .OrderByDescending(s => s.CreatedUTC)
                        .First();

        CreateProjection<SourceType, DestinationDto>(MemberList.Destination)
            .ForCtorParam(nameof(DestinationDto.Id), opt => opt.MapFrom(src => src.Id))
            .ForCtorParam(nameof(DestinationDto.StatusDate), 
                opt => opt.MapFrom(src => getTargetStatus.Invoke(src).CreatedUTC))
            .ForCtorParam(nameof(DestinationDto.Open), 
                opt => opt.MapFrom(src => getTargetStatus.Invoke(src).StatusType!.IsOpen.ToString()))
            .ForCtorParam(nameof(DestinationDto.Status),
                opt => opt.MapFrom(src => getTargetStatus.Invoke(src).StatusType!.Label))
            .ForCtorParam(nameof(DestinationDto.Created), opt => opt.MapFrom(src => src.CreatedUTC));
    }
}

这种方式直接在Profile内复用逻辑,无需额外类,适合简单场景。

方案2:封装为扩展方法

如果需要在多个Profile或映射中复用这段逻辑,可以把它封装成静态扩展方法,返回对应的表达式树:

public static class SourceTypeMappingExtensions
{
    // 基础表达式:获取目标Status
    private static readonly Expression<Func<SourceType, Status>> _getTargetStatus = 
        src => src.Statuses.Where(s => s.StatusType!.StatusGroupId == 1)
                          .OrderByDescending(s => s.CreatedUTC)
                          .First();

    // 封装各属性的表达式
    public static Expression<Func<SourceType, DateTime>> GetTargetStatusDate()
    {
        return src => _getTargetStatus.Invoke(src).CreatedUTC;
    }

    public static Expression<Func<SourceType, string>> GetTargetStatusOpenFlag()
    {
        return src => _getTargetStatus.Invoke(src).StatusType!.IsOpen.ToString();
    }

    public static Expression<Func<SourceType, string>> GetTargetStatusLabel()
    {
        return src => _getTargetStatus.Invoke(src).StatusType!.Label;
    }
}

然后在Profile中直接使用这些扩展方法:

public class DestinationProfile : Profile
{
    public DestinationProfile()
    {
        CreateProjection<SourceType, DestinationDto>(MemberList.Destination)
            .ForCtorParam(nameof(DestinationDto.Id), opt => opt.MapFrom(src => src.Id))
            .ForCtorParam(nameof(DestinationDto.StatusDate), opt => opt.MapFrom(SourceTypeMappingExtensions.GetTargetStatusDate()))
            .ForCtorParam(nameof(DestinationDto.Open), opt => opt.MapFrom(SourceTypeMappingExtensions.GetTargetStatusOpenFlag()))
            .ForCtorParam(nameof(DestinationDto.Status), opt => opt.MapFrom(SourceTypeMappingExtensions.GetTargetStatusLabel()))
            .ForCtorParam(nameof(DestinationDto.Created), opt => opt.MapFrom(src => src.CreatedUTC));
    }
}

这种方式更适合逻辑需要多处复用的场景,代码更整洁,也便于维护。

注意事项

  • 两种方案都基于表达式树,完全兼容CreateProjection的LINQ查询场景,不会影响ORM生成SQL的能力。
  • 如果你的映射不是投影(而是普通的内存对象映射),还可以使用ResolveUsing先获取Status对象,再在后续映射中复用,但这种方式不适用于LINQ投影场景。

内容的提问来源于stack exchange,提问作者Archigo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 23:50:44