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

TL; DR:我遇到了多态映射问题。 我用一个测试套件制作了一个github repo来说明我的问题。 请在此处找到: LINK TO REPO

我正在努力实现保存/加载function。 为了实现这一点,我需要确保我序列化的域模型以序列化友好的方式表示。 为了实现这一点,我创建了一组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 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 entries = new List(); public IEnumerable Entries => this.entries; public void Add(IDto entry) { this.entries.Add(entry); } } 

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

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

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

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

 public class CollectionMappingProfile : Profile { public CollectionMappingProfile() { this.CreateMap().ForMember(m => m.P0, a => a.MapFrom(x => x.Prop0)).ReverseMap(); this.CreateMap(). ForMember(fc => fc.Entries, opt => opt.Ignore()). AfterMap((tc, fc, ctx) => fc.Entries = tc.Entries.Select(e => ctx.Mapper.Map(e)).ToArray()); this.CreateMap(). AfterMap((fc, tc, ctx) => { foreach (var t in fc.Entries.Select(e => ctx.Mapper.Map(e))) tc.Add(t); }); } public class DomainProfile1 : Profile { public DomainProfile1() { this.CreateMap().ForMember(m => m.P1, a => a.MapFrom(x => x.Prop1)) .IncludeBase().ReverseMap(); } } public class DomainProfile2 : Profile { public DomainProfile2() { this.CreateMap().ConstructUsing(f => new Dto2()).As(); this.CreateMap().ForMember(m => m.P2, a => a.MapFrom(x => x.Prop2)) .IncludeBase().ReverseMap(); } } 

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

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

这是github repo的另一个链接

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

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

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

2)我没有正确地完全定义映射inheritance。 我会把它分解。

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

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

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

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

现在,显式定义了映射的每个方向,并且遵循inheritance。

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

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

有用的链接:

这个

这是一个很好的重构练习。 我不可否认地把它作为建立我的榜样的起点。 所以,谢谢@Olivier。