【问题标题】:Polymorphic Mapping of Collections with AutoMapper使用 AutoMapper 对集合进行多态映射
【发布时间】:2017-02-14 12:55:13
【问题描述】:

TL;DR:我遇到了多态映射问题。我制作了一个 github 存储库,其中包含一个说明我的问题的测试套件。请在这里找到它:LINK TO REPO

我正在努力实现保存/加载功能。为此,我需要确保我正在序列化的域模型以序列化友好的方式表示。为此,我创建了一组 DTO,其中包含进行有意义的保存或加载所需的最少信息集。

类似这样的域:

public interface IDomainType
{
  int Prop0 { get; set; }
}

public class DomainType1 : IDomainType
{
  public int Prop1 { get; set; }
  public int Prop0 { get; set; }
}

public class DomainType2 : IDomainType
{
  public int Prop2 { get; set; }
  public int Prop0 { get; set; }
}

public class DomainCollection
{
  public IEnumerable<IDomainType> Entries { get; set; }
}

...对于 DTO

public interface IDto
{
  int P0 { get; set; }
}

public class Dto1 : IDto
{
  public int P1 { get; set; }
  public int P0 { get; set; }
}

public class Dto2 : IDto
{
  public int P2 { get; set; }
  public int P0 { get; set; }
}

public class DtoCollection
{
  private readonly IList<IDto> entries = new List<IDto>();
  public IEnumerable<IDto> Entries => this.entries;
  public void Add(IDto entry) { this.entries.Add(entry); }
}

这个想法是 DomainCollection 代表应用程序的当前状态。目标是将 DomainCollection 映射到 DtoCollection 会生成一个 DtoCollection 实例,其中包含映射到域的 IDto 的适当实现。反之亦然。

这里有一点额外的技巧是不同的具体域类型来自不同的插件程序集,所以我需要找到一种优雅的方式让 AutoMapper(或类似的,如果你知道更好的映射框架)完成繁重的工作我。

使用结构映射,我已经能够从插件中找到并加载所有配置文件,并使用它们配置应用程序 IMapper。

我尝试过创建这样的配置文件...

public class CollectionMappingProfile : Profile
{
  public CollectionMappingProfile()
  {
    this.CreateMap<IDomainType, IDto>().ForMember(m => m.P0, a => a.MapFrom(x => x.Prop0)).ReverseMap();

    this.CreateMap<DtoCollection, DomainCollection>().
       ForMember(fc => fc.Entries, opt => opt.Ignore()).
       AfterMap((tc, fc, ctx) => fc.Entries = tc.Entries.Select(e => ctx.Mapper.Map<IDomainType>(e)).ToArray());

    this.CreateMap<DomainCollection, DtoCollection>().
       AfterMap((fc, tc, ctx) =>
                {
                  foreach (var t in fc.Entries.Select(e => ctx.Mapper.Map<IDto>(e))) tc.Add(t);
                });
}

public class DomainProfile1 : Profile
{
  public DomainProfile1()
  {
    this.CreateMap<DomainType1, Dto1>().ForMember(m => m.P1, a => a.MapFrom(x => x.Prop1))
      .IncludeBase<IDomainType, IDto>().ReverseMap();
  }
}

public class DomainProfile2 : Profile
{
  public DomainProfile2()
  {
    this.CreateMap<DomainType2, IDto>().ConstructUsing(f => new Dto2()).As<Dto2>();

    this.CreateMap<DomainType2, Dto2>().ForMember(m => m.P2, a => a.MapFrom(x => x.Prop2))
      .IncludeBase<IDomainType, IDto>().ReverseMap();
  }
}

然后我编写了一个测试套件,以确保在需要将此功能与应用程序集成时映射将按预期运行。我发现每当 DTO 被映射到域(想想加载)时,AutoMapper 都会创建 IDomainType 代理,而不是将它们解析到域。

我怀疑问题出在我的映射配置文件上,但我的人才已经用完了。提前感谢您的意见。

Here's another link to the github repo

【问题讨论】:

    标签: c# .net automapper automapper-5


    【解决方案1】:

    我自己在研究多态映射问题时偶然发现了这个问题。答案很好,但如果您想从基本映射的角度来处理它并拥有许多派生类,这只是另一种选择,您可以尝试以下方法:

    CreateMap<VehicleEntity, VehicleDto>()
        .IncludeAllDerived();
    
    CreateMap<CarEntity, CarDto>();
    CreateMap<TrainEntity, TrainDto>();
    CreateMap<BusEntity, BusDto>();
    

    请参阅automapper docs 了解更多信息。

    【讨论】:

      【解决方案2】:

      我花了一点时间重新组织回购。我甚至模仿了一个核心项目和两个插件。这确保了当测试最终开始通过时,我不会得到假阳性结果。

      我发现该解决方案有两个(ish)部分。

      1) 我在滥用 AutoMapper 的 .ReverseMap() 配置方法。我假设它会执行我正在做的任何自定义映射的倒数。不是这样!它只做简单的逆转。很公平。一些关于它的问题/答案: 1, 2

      2) 我没有正确定义映射继承。我会分解的。

      2.1) My DomainProfiles 遵循以下模式:

      public class DomainProfile1 : Profile
      {
        public DomainProfile1()
        {
          this.CreateMap<DomainType1, IDto>().ConstructUsing(f => new Dto1()).As<Dto1>();
          this.CreateMap<DomainType1, Dto1>().ForMember(m => m.P1, a => a.MapFrom(x => x.Prop1))
            .IncludeBase<IDomainType, IDto>().ReverseMap();
      
          this.CreateMap<Dto1, IDomainType>().ConstructUsing(dto => new DomainType1()).As<DomainType1>();
        }
      }
      

      所以现在知道 .ReverseMap() 不是在这里使用的东西,很明显 Dto1 和 DomainType1 之间的映射定义不好。此外,DomainType1 和 IDto 之间的映射没有链接回基本 IDomainType 到 IDto 的映射。也是一个问题。最终结果:

      public class DomainProfile1 : Profile
      {
        public DomainProfile1()
        {
          this.CreateMap<DomainType1, IDto>().IncludeBase<IDomainType, IDto>().ConstructUsing(f => new Dto1()).As<Dto1>();
          this.CreateMap<DomainType1, Dto1>().IncludeBase<DomainType1, IDto>().ForMember(m => m.P1, a => a.MapFrom(x => x.Prop1));
      
          this.CreateMap<Dto1, IDomainType>().IncludeBase<IDto, IDomainType>().ConstructUsing(dto => new DomainType1()).As<DomainType1>();
          this.CreateMap<Dto1, DomainType1>().IncludeBase<Dto1, IDomainType>().ForMember(m => m.Prop1, a => a.MapFrom(x => x.P1));
        }
      }
      

      现在映射的每个方向都被明确定义,并且继承得到尊重。

      2.2) IDomainType 和 IDto 的最基本映射位于配置文件内部,该配置文件还定义了“集合”类型的映射。这意味着一旦我拆分项目以模仿插件架构,只测试最简单继承的测试以新的方式失败 - 找不到基本映射。我所要做的就是将这些映射放入他们自己的配置文件中,并在测试中使用该配置文件。这很好SRP

      在我将自己的答案标记为接受的答案之前,我会将我学到的知识应用到我的实际项目中。希望我已经掌握了它,并希望这对其他人有帮助。

      有用的链接:

      this

      this one 是一个很好的重构练习。诚然,我将它用作构建示例的起点。所以,谢谢@Olivier。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-03-18
        • 1970-01-01
        • 2020-12-12
        • 1970-01-01
        • 1970-01-01
        • 2016-04-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多