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
相关产品推荐
相关产品推荐

