使用AutoMapper的集合的多态映射 [英] Polymorphic Mapping of Collections with AutoMapper

查看:96
本文介绍了使用AutoMapper的集合的多态映射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TL; DR:我在使用多态映射时遇到了麻烦.我用一个测试套件做了一个github回购,它说明了我的问题.请在这里找到它:链接到REPO

TL;DR: I'm having trouble with Polymorphic mapping. I've made a github repo with a test suite that illustrates my issue. Please find it here: LINK TO REPO

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

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.

对于域来说,是这样的

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的适当实现.反之亦然.

The idea is that DomainCollection represents the current state of the application. 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. And vice versa.

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

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.

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

Using structuremap, I'm already able to locate and load all the profiles from the plugins and configure the applications IMapper with them.

我试图创建像这样的配置文件...

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

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

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.

我怀疑问题出在我的映射配置文件上,但是我已经用完了人才.预先感谢您的输入.

I suspect the problem is with my mapping profiles, but I've run out of talent. Thanks in advance for your input.

这是指向github存储库的另一个链接

推荐答案

我花了一些时间来整理存储库.我尽力模仿一个核心项目和两个插件.这样可以确保在测试最终开始通过时,我不会以假阳性结果告终.

I spent a little time reorganizing the 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.

1)我在滥用AutoMapper的.ReverseMap()配置方法.我以为它会执行我正在做的任何自定义映射的倒数.不是这样!它仅执行简单的冲销.很公平.关于此的一些SO问题/答案: 1

1) I was abusing AutoMapper's .ReverseMap() configuration method. 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

2)我没有正确定义映射继承.我将其分解.

2) I wasn't fully defining the mapping inheritance properly. I'll break it down.

2.1)我的DomainProfiles遵循以下模式:

2.1) My DomainProfiles followed this pattern:

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的映射.也是一个问题.最终结果:

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. Also, The mapping between DomainType1 and IDto didn't link back to the base IDomainType to IDto mapping. 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)IDomainType和IDto的最基本映射位于配置文件中,该配置文件还定义了集合"类型的映射.这意味着一旦我将项目分解为模仿插件体系结构,仅测试最简单继承的测试将以新方式失败-无法找到基本映射.我要做的就是将这些映射放入自己的配置文件中,并在测试中也使用该配置文件. SRP .

2.2) The most base mapping for IDomainType and IDto was inside of the profile that also defined the mappings for the "collection" types. 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.

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

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.

有用的链接:

这一个很好重构运动.坦白地说,我以此为起点来建立自己的榜样.所以,谢谢@Olivier.

this one was a good refactoring exercise. I admittedly used it as a starting place to build up my example. So, thanks @Olivier.

这篇关于使用AutoMapper的集合的多态映射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆