TL; DR:我遇到了多态映射问题。我用一个测试套件制作了一个github repo来说明我的问题。请在此处找到: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的代理,而不是将它们解析为域。
我怀疑问题在于我的地图配置文件,但我已经没有人才了。提前感谢您的意见。
答案 0 :(得分:0)
我花了一点时间重新组织回购。我甚至模仿了一个核心项目和两个插件。这确保了当测试最终开始通过时,我不会得到假阳性结果。
我发现解决方案有两个(ish)部分。
1)我在滥用AutoMapper的.ReverseMap()配置方法。我假设它将执行我正在做的任何自定义映射的倒数。不是这样!它只做简单的反转。很公平。关于它的一些SO问题/答案: 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的最基本映射在配置文件中,该配置文件也定义了&#34;集合&#34;的映射。类型。这意味着,一旦我将项目拆分为模仿插件架构,那些仅测试最简单的继承的测试就会以新的方式失败 - 无法找到基本映射。我所要做的就是将这些映射放入他们自己的配置文件中,并在测试中使用该配置文件。这很好SRP。
在我将自己的答案标记为已接受的答案之前,我会将我所学到的知识运用到我的实际项目中。希望我能得到它,并希望这对其他人有所帮助。
有用的链接:
this one是一次很好的重构练习。我不可否认地把它作为建立我的榜样的起点。所以,谢谢@Olivier。
答案 1 :(得分:0)
当我自己查看多态映射问题时,我偶然发现了这个问题。答案是好的,但是如果您想从基本映射的角度来解决这个问题,并且有很多派生类,这是另一个选择,您可以尝试以下方法:
CreateMap<VehicleEntity, VehicleDto>()
.IncludeAllDerived();
CreateMap<CarEntity, CarDto>();
CreateMap<TrainEntity, TrainDto>();
CreateMap<BusEntity, BusDto>();
有关更多信息,请参见automapper docs。