AutoMapper中的集合多态映射

8

简述:我在使用多态映射时遇到了问题。我创建了一个GitHub存储库,其中包含一个测试套件,以说明我的问题。请在此处查找链接: LINK TO REPO

我正在实施保存/加载功能。为了实现这一点,我需要确保我正在序列化的领域模型以一种序列化友好的方式呈现。为此,我创建了一组DTOs,其中包含执行有意义的保存或加载所需的最少信息。

如下是领域模型的示例:

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,从而得到一个包含IDto适当实现的DtoCollection实例,这些实现与域映射相对应。反之亦然。

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

使用StructureMap,我已经能够定位和加载所有插件中的配置文件,并使用它们配置应用程序的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被映射到Domain(考虑Load)时,AutoMapper会创建IDomainType的代理,而不是将其解析为域。
我怀疑问题出在我的映射配置文件中,但我已经无法解决了。感谢您提前的帮助。
这里是另一个GitHub存储库的链接:Here's another link to the github repo
2个回答

3

我在自己研究多态映射问题时偶然发现了这个问题。回答是很好的,但如果您想从基本映射角度并且存在许多派生类来处理它,那么还有另一个选项:

CreateMap<VehicleEntity, VehicleDto>()
    .IncludeAllDerived();

CreateMap<CarEntity, CarDto>();
CreateMap<TrainEntity, TrainDto>();
CreateMap<BusEntity, BusDto>();

查看automapper文档获取更多信息。


1

我花了一点时间重新组织了代码库。我甚至模仿了一个核心项目和两个插件。这样可以确保在测试最终通过时不会出现误报结果。

我发现解决方案有两个(或者说是两个半)部分。

1) 我滥用了AutoMapper的.ReverseMap()配置方法。我假设它会执行与我进行的任何自定义映射的相反操作。但实际上并不是这样!它只执行简单的翻转。对此有一些关于它的stackoverflow问题/答案: 1, 2

2) 我没有完全正确地定义映射继承。我来详细说明一下。

2.1) 我的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的最基本映射位于定义“collection”类型映射的配置文件中。这意味着一旦我将项目拆分以模拟插件架构,仅测试最简单继承的测试会以新的方式失败 - 找不到基本映射。我所要做的就是将这些映射放入它们自己的配置文件中,并在测试中使用该配置文件。这只是良好的SRP

我会将所学应用于我的实际项目,然后将自己的答案标记为被接受的答案。希望我已经掌握了它,也希望这对其他人有所帮助。

有用的链接:

this

这个是一个很好的重构练习。我承认我把它用作构建我的示例的起点。所以,感谢@Olivier。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接