【问题标题】:Map class with abstract property to destination将具有抽象属性的类映射到目的地
【发布时间】:2020-08-06 22:16:30
【问题描述】:

我在将包含抽象属性的容器类映射到我的视图模型目标类时遇到了一些问题。

映射源类

//Container class
public class GiftcardDetailResponse : Response
{
    //Instance of either UserGiftcardDTO or ImportedGiftcardDTO
    public GiftcardInstanceDTO UserGiftcard { get; set; }
}

public abstract class GiftcardInstanceDTO : BaseDTO
{
    public int UserId { get; set; }   
    public decimal Balance { get; set; } 
    public string BarcodeValue { get; set; }
    public string BarcodeUrl { get; set; }
    public string Code { get; set; }
    public string Pin { get; set; }
    public bool RefreshBalanceSupported { get; set; }
    public bool Viewed { get; set; }
    public bool IsArchived { get; set; }
    public virtual UserDTO User { get; set; }
}

public class UserGiftcardDTO : GiftcardInstanceDTO
{
    public int GiftcardId { get; set; }
    public DateTimeOffset? ActivatedAt { get; set; }
    public DateTimeOffset? BalanceUpdatedAt { get; set; }
    public string ClaimUrl { get; set; }
    public string ClaimSecret { get; set; }
    public PrivacySettings Privacy { get; set; }
    public bool IsPending { get; set; }
    public bool BoughtAsGift { get; set; }
    public virtual GiftcardDTO Giftcard { get; set; }
}

public class ImportedGiftcardDTO : GiftcardInstanceDTO
{
    public string RetailerName { get; set; }
    public string FrontImage { get; set; }
    public string BackImage { get; set; }
}

映射目标类

//Front-end view model
public class GiftcardDetailViewModel
{
    public int Id { get; set; }
    public string RetailerName { get; set; }
    public decimal Amount { get; set; }
    public string Image { get; set; }
}

映射配置

        CreateMap<GiftcardDetailResponse, GiftcardDetailViewModel>()
            .IncludeMembers(src => src.UserGiftcard);

        //Automapper should pick a concrete mapping for this
        CreateMap<GiftcardInstanceDTO, GiftcardDetailViewModel>()
            .IncludeAllDerived();

        //Concrete mappings to View Model
        CreateMap<UserGiftcardDTO, GiftcardDetailViewModel>()
            .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
            .ForMember(dest => dest.Image, opt => opt.MapFrom(src => src.Giftcard.Image))
            .ForMember(dest => dest.Amount, opt => opt.MapFrom(src => src.Balance))
            .ForMember(dest => dest.RetailerName, opt => opt.MapFrom(src => src.Giftcard.Merchant.Name));

        CreateMap<ImportedGiftcardDTO, GiftcardDetailViewModel>()
            .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
            .ForMember(dest => dest.Image, opt => opt.MapFrom(src => src.FrontImage))
            .ForMember(dest => dest.Amount, opt => opt.MapFrom(src => src.Balance))
            .ForMember(dest => dest.RetailerName, opt => opt.MapFrom(src => src.RetailerName));

问题是当我将GiftcardDetailResponse 映射到GiftcardDetailViewModel 时,Automapper 没有为我的抽象属性的派生类选择我的显式映射。例如,如果我的代码看起来像这样

        var containerClass = new GiftcardDetailResponse();
        containerClass.UserGiftcard = new ImportedGiftcardDTO();

        var viewModel = MapperWrapper.Mapper.Map<GiftcardDetailViewModel>(containerClass);

执行树是这样的

//Automapper generated execution plan
(src, dest, ctxt) =>
{
    GiftcardDetailViewModel typeMapDestination;
    return (src == null)
    ? null
    : {
        typeMapDestination = dest ?? new GiftcardDetailViewModel();
        try
        {
            var resolvedValue = ((src == null) || ((src.UserGiftcard == null) || false)) ? default(int) : src.UserGiftcard.Id;
            typeMapDestination.Id = resolvedValue;
        }
        catch (Exception ex)
        {
            throw new AutoMapperMappingException(
                "Error mapping types.",
                ex,
                AutoMapper.TypePair,
                TypeMap,
                PropertyMap);

            return default(int);
        }

        return typeMapDestination;
    };
}

似乎只选择GiftcardInstanceDTOGiftcardDetailViewModel 中具有相同名称的属性,而不是使用我定义的映射。

但是当我只将抽象属性显式映射到我的视图模型时,我寻找的内容类似于执行树,例如

var propertyModel = MapperWrapper.Mapper.Map<GiftcardDetailViewModel>(containerClass.UserGiftcard);

正确显示了我的派生映射

//Automapper generated execution plan
(src, dest, ctxt) =>
{
GiftcardDetailViewModel typeMapDestination;
return (src == null)
    ? null
    : {
        typeMapDestination = dest ?? new GiftcardDetailViewModel();
        try
        {
            var resolvedValue =
            {
                try
                {
                    return ((src == null) || false) ? default(int) : src.Id;
                }
                catch (NullReferenceException)
                {
                    return default(int);
                }
                catch (ArgumentNullException)
                {
                    return default(int);
                }
            };

            typeMapDestination.Id = resolvedValue;
        }
        catch (Exception ex)
        {
            throw new AutoMapperMappingException(
                "Error mapping types.",
                ex,
                AutoMapper.TypePair,
                TypeMap,
                PropertyMap);

            return default(int);
        }
        try
        {
            var resolvedValue =
            {
                try
                {
                    return ((src == null) || false) ? null : src.RetailerName;
                }
                catch (NullReferenceException)
                {
                    return null;
                }
                catch (ArgumentNullException)
                {
                    return null;
                }
            };

            var propertyValue = (resolvedValue == null) ? null : resolvedValue;
            typeMapDestination.RetailerName = propertyValue;
        }
        catch (Exception ex)
        {
            throw new AutoMapperMappingException(
                "Error mapping types.",
                ex,
                AutoMapper.TypePair,
                TypeMap,
                PropertyMap);

            return null;
        }
        try
        {
            var resolvedValue =
            {
                try
                {
                    return ((src == null) || false) ? null : src.FrontImage;
                }
                catch (NullReferenceException)
                {
                    return null;
                }
                catch (ArgumentNullException)
                {
                    return null;
                }
            };

            var propertyValue = (resolvedValue == null) ? null : resolvedValue;
            typeMapDestination.Image = propertyValue;
        }
        catch (Exception ex)
        {
            throw new AutoMapperMappingException(
                "Error mapping types.",
                ex,
                AutoMapper.TypePair,
                TypeMap,
                PropertyMap);

            return null;
        }
        try
        {
            var resolvedValue =
            {
                try
                {
                    return ((src == null) || false) ? default(decimal) : src.Balance;
                }
                catch (NullReferenceException)
                {
                    return default(decimal);
                }
                catch (ArgumentNullException)
                {
                    return default(decimal);
                }
            };

            typeMapDestination.Amount = resolvedValue;
        }
        catch (Exception ex)
        {
            throw new AutoMapperMappingException(
                "Error mapping types.",
                ex,
                AutoMapper.TypePair,
                TypeMap,
                PropertyMap);

            return default(decimal);
        }

        return typeMapDestination;
    };
}

文档说当已经定义映射时,我可以使用IncludeMembers 将子对象展平到目标对象。但是当子对象是抽象的时,这种行为在这种情况下似乎不能正常工作。

【问题讨论】:

  • IncludeMembers 并非旨在以这种方式工作。您可以使用AfterMap 调用Map 并传递目标对象。
  • 只是因为我的子成员是抽象的吗?因为文档中显示的示例非常相似docs.automapper.org/en/stable/Flattening.html#includemembers
  • 不,这是因为子成员的映射在运行时没有解析,是预先解析的,在配置时,它是静态的。当您调用Map 时,Include 通过在运行时解析地图来工作。
  • 到目前为止,添加 AfterMap 似乎不起作用。如果我尝试用我的派生映射覆盖目标,则只返回一个空白对象。我会尝试添加一个自定义解析器,看看是否可行
  • AfterMap((source, destination, context) =&gt; context.Map(source.UserGiftcard, destination)

标签: c# inheritance polymorphism automapper automapper-9


【解决方案1】:

IncludeMembers 没有实现这种“动态”行为,但你可以这样做:

 CreateMap<Source, Destination>().AfterMap((source, destination, context) => context.Mapper.Map(source.InnerSource, destination));

【讨论】:

  • 正因为如此,我正要提交一个 GitHub 问题。关于在配置时与运行时、静态与动态解决的评论非常有助于理解正在发生的事情。如果能够从高级别的角度了解 Automapper 在文档中的工作原理,以避免这种类型的陷阱。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-23
  • 1970-01-01
相关资源
最近更新 更多