Automapper 引用自定义成员映射中的现有子类型映射

Automapper refer to existing subtype mapping in custom member mapping

我的数据库实体如下所示(已编辑相关信息):

public class Task
{
    public Guid? StatusId { get; set; }
    public Status Status { get; set; }

    public DateTime? StatusLastModified { get; set; }
}

public class Status
{
    public string Name { get; set; }
    // several other properties redacted
}

任务最初没有状态(StatusLastModified 也为空),一旦分配了第一个状态,任务将始终有一个状态(并且 StatusLastModified 也不为空)。

为了这个问题,您可以假设这些属性将始终为 null 或非 null,我不需要考虑一个为 null 而另一个不为 null 的异常情况。

对于我的 DTO,我想将其映射到以下结构:

public struct TaskDto
{
    public TaskStatusDto? Status { get; set; }
}

public struct TaskStatusDto
{
    public DateTime Timestamp { get; set; } // maps from Task.StatusLastModified 
    public StatusDto Status { get; set; } // maps from Task.Status
}

public struct StatusDto
{
    public string Name { get; set; }
    // and all the other properties from the Status entity
}

这里的目标是我的TaskDto只有在有实际状态的情况下才有底层TaskStatusDto,否则应该为null。这很有用,因为它不需要不断空检查 both 状态对象和上次修改日期。

但是,我很难在 Automapper 中配置此映射。我已经在 StatusStatusDto 之间创建了映射:

configuration
     .CreateMap<Status, StatusDto>();

我的真实映射更复杂,但它也被证明有效,因此您可以假设这是一个有效且有效的映射。

我面临的问题是如何在 TaskTaskDto 之间创建自定义映射时引用此映射。我目前尝试了以下方法:

configuration
    .CreateMap<Task, TaskDto>()
    .ForMember(
            dto => dto.Status,
            opt => opt.MapFrom(entity => 
                entity.Status == null
                    ? null as TaskStatusDto?
                    : new TaskStatusDto()
                    {
                        TimeStamp = entity.StatusLastModified.Value,
                        Status = // ??? make me a StatusDto from entity.Status
                    }
        ));

不知道怎么填的部分是评论。我可以访问原始实体 (entity.Status),但我不知道如何告诉 Automapper 根据它应该已经知道的映射将此对象转换为 StatusDto

我的方法也感觉比它应该的更做作,但我不知道如何引入这个中间 TaskStatusDto 对象,如果它没有得到我的源数据中的实际实体的支持。


一个小脚注,不确定是否相关:我正在使用 Heroic.Automapper,这意味着 TaskDtoStatusDto 映射是在不同的位置配置的(即在 TaskDtoStatusDto class,Heroic 框架将在运行时组合所有这些映射。

public class TaskDto : IMapFrom<Task>, IHaveCustomMappings
{
     public void CreateMappings(IMapperConfigurationExpression configuration)
     {
         // mapping from Task to TaskDto
     }
}

public class StatusDto : IMapFrom<Status>, IHaveCustomMappings
{
     public void CreateMappings(IMapperConfigurationExpression configuration)
     {
         // mapping from Status to StatusDto
     }
}

然而,据我所知,地图本身的设置并不是 Heroic 特有的,应该是纯粹的 Automapper 语法。

我认为更简洁的解决方案是创建您自己的 CustomResolver

public class TaskStatusDtoResolver : IValueResolver<Task, TaskDto, TaskStatusDto?>
{
    public TaskStatusDto? Resolve(Task source, TaskDto destination, TaskStatusDto? member, ResolutionContext context)
    {
        if (source.Status == null)
        {
            return null;
        }

        return new TaskStatusDto
        {
            Status = context.Mapper.Map<StatusDto>(source.Status),
            Timestamp = source.StatusLastModified.Value
        };
    }
}

并且您可以使用以下配置:

cfg.CreateMap<Status, StatusDto>();
cfg.CreateMap<Task, TaskDto>()
    .ForMember(dest => dest.Status, opt => opt.MapFrom<TaskStatusDtoResolver>());

See the working example here


或者您可以在配置中定义所有映射:

var configuration = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Status, StatusDto>();
    cfg.CreateMap<Task, TaskDto>().ForMember(
         dest => dest.Status,
         src => src.MapFrom((task, taskDto, member, context) =>
         {
             return task.Status == null ? null as TaskStatusDto? : new TaskStatusDto()
             {
                 Timestamp = task.StatusLastModified.Value,
                 Status = context.Mapper.Map<StatusDto>(task.Status)
             };
         }
     ));
});