简体   繁体   English

使用 AutoMapper 对集合进行多态映射

[英]Polymorphic Mapping of Collections with AutoMapper

TL;DR: I'm having trouble with Polymorphic mapping. TL;DR:我在使用多态映射时遇到了麻烦。 I've made a github repo with a test suite that illustrates my issue.我制作了一个带有测试套件的 github repo 来说明我的问题。 Please find it here: LINK TO REPO请在这里找到它:链接到回购

I'm working on implementing a save/load feature.我正在努力实现保存/加载功能。 To accomplish this, I need to make sure the domain model that I'm serializing is represented in a serialization-friendly way.为了实现这一点,我需要确保我正在序列化的域模型以序列化友好的方式表示。 To accomplish this I've created a set of DTOs that contain the bare-minimum set of information required to do a meaningful save or load.为了实现这一点,我创建了一组 DTO,其中包含执行有意义的保存或加载所需的最低限度的信息集。

Something like this for the domain:域的类似内容:

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; }
}

...and for the DTOs ...对于 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); }
}

The idea is that DomainCollection represents the current state of the application.这个想法是 DomainCollection 表示应用程序的当前状态。 The goal is that mapping DomainCollection to DtoCollection results in an instance of DtoCollection that contains the appropriate implementations of IDto as they map to the domain.目标是将 DomainCollection 映射到 DtoCollection 会生成一个 DtoCollection 实例,该实例包含映射到域的 IDto 的适当实现。 And vice versa.反之亦然。

A little extra trick here is that the different concrete domain types come from different plugin assemblies, so I need to find an elegant way to have AutoMapper (or similar, if you know of a better mapping framework) do the heavy lifting for me.这里有一个额外的技巧,不同的具体域类型来自不同的插件程序集,因此我需要找到一种优雅的方式让 AutoMapper(或类似的,如果您知道更好的映射框架)为我完成繁重的工作。

Using structuremap, I'm already able to locate and load all the profiles from the plugins and configure the applications IMapper with them.使用结构图,我已经能够从插件中定位和加载所有配置文件,并使用它们配置应用程序 IMapper。

I've tried to create the profiles like this...我试图创建这样的配置文件......

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();
  }
}

I then wrote a test suite to make sure that the mapping will behave as expected when its time to integrate this feature with the application.然后,我编写了一个测试套件,以确保在需要将此功能与应用程序集成时,映射的行为符合预期。 I found whenever DTOs were getting mapped to Domain (think Load) that AutoMapper would create proxies of IDomainType instead of resolving them to the domain.我发现每当 DTO 被映射到域(想想负载)时,AutoMapper 都会创建 IDomainType 的代理,而不是将它们解析到域。

I suspect the problem is with my mapping profiles, but I've run out of talent.我怀疑问题出在我的映射配置文件上,但我的人才用完了。 Thanks in advance for your input.预先感谢您的意见。

Here's another link to the github repo这是github repo的另一个链接

I spent a little time reorganizing the repo.我花了一点时间重新组织 repo。 I went as far as to mimic a core project and two plugins.我什至模仿了一个核心项目和两个插件。 This made sure that I wouldn't end up with a false-positive result when the tests finally started passing.这确保了当测试最终开始通过时,我不会得到假阳性结果。

What I found was that the solution had two(ish) parts to it.我发现该解决方案有两个(ish)部分。

1) I was abusing AutoMapper's .ReverseMap() configuration method. 1) 我滥用了 AutoMapper 的 .ReverseMap() 配置方法。 I was assuming that it would perform the reciprocal of whatever custom mapping I was doing.我假设它会执行我正在做的任何自定义映射的互惠。 Not so!不是这样! It only does simple reversals.它只做简单的逆转。 Fair enough.够公平的。 Some SO questions/answers about it: 1 , 2一些关于它的问题/答案: 1 , 2

2) I wasn't fully defining the mapping inheritance properly. 2)我没有完全正确地定义映射继承。 I'll break it down.我会分解它。

2.1) My DomainProfiles followed this pattern: 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>();
  }
}

So now knowing that .ReverseMap() is not the thing to use here, it becomes obvious that the map between Dto1 and DomainType1 was poorly defined.所以现在知道 .ReverseMap() 不是在这里使用的东西,很明显 Dto1 和 DomainType1 之间的映射定义不明确。 Also, The mapping between DomainType1 and IDto didn't link back to the base IDomainType to IDto mapping.此外,DomainType1 和 IDto 之间的映射没有链接回基本的 IDomainType 到 IDto 映射。 Also an issue.也是一个问题。 The final result:最终结果:

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));
  }
}

Now each direction of the mapping is explicitly defined, and the inheritance is respected.现在映射的每个方向都被明确定义,并且继承受到尊重。

2.2) The most base mapping for IDomainType and IDto was inside of the profile that also defined the mappings for the "collection" types. 2.2) IDomainType 和 IDto 的最基本映射位于配置文件内,该配置文件还定义了“集合”类型的映射。 This meant that once I had split up the project to mimic a plugin architecture, the tests that only tested the simplest inheritances failed in new ways - The base mapping couldn't be found.这意味着一旦我将项目拆分以模拟插件架构,仅测试最简单继承的测试就会以新的方式失败——无法找到基础映射。 All I had to do was put these mappings into their own profile and use that profile in the tests as well.我所要做的就是将这些映射放入它们自己的配置文件中,并在测试中也使用该配置文件。 That's just good SRP .这只是很好的SRP

I'll apply what I've learned to my actual project before I mark my own answer as the accepted answer.在将我自己的答案标记为已接受的答案之前,我会将我学到的知识应用到我的实际项目中。 Hopefully I've got it and hopefully this will be helpful to others.希望我得到了它,并希望这对其他人有帮助。

Useful links:有用的链接:

this

this one was a good refactoring exercise. 是一个很好的重构练习。 I admittedly used it as a starting place to build up my example.我无可否认地使用它作为构建我的示例的起点。 So, thanks @Olivier.所以,谢谢@Olivier。

I stumbled across this question when looking in to a polymorphic mapping issue myself.我自己在查看多态映射问题时偶然发现了这个问题。 The answer is good, but just another option if you'd like to approach it from the base mapping perspective and have many derived classes, you can try the following:答案是好的,但如果您想从基本映射的角度来处理它并且有许多派生类,那么这只是另一种选择,您可以尝试以下操作:

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

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

See the automapper docs for more info.有关更多信息,请参阅automapper 文档

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM