簡體   English   中英

使用 AutoMapper 對集合進行多態映射

[英]Polymorphic Mapping of Collections with AutoMapper

TL;DR:我在使用多態映射時遇到了麻煩。 我制作了一個帶有測試套件的 github 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 的代理,而不是將它們解析到域。

我懷疑問題出在我的映射配置文件上,但我的人才用完了。 預先感謝您的意見。

這是github repo的另一個鏈接

我花了一點時間重新組織 repo。 我什至模仿了一個核心項目和兩個插件。 這確保了當測試最終開始通過時,我不會得到假陽性結果。

我發現該解決方案有兩個(ish)部分。

1) 我濫用了 AutoMapper 的 .ReverseMap() 配置方法。 我假設它會執行我正在做的任何自定義映射的互惠。 不是這樣! 它只做簡單的逆轉。 夠公平的。 一些關於它的問題/答案: 1 , 2

2)我沒有完全正確地定義映射繼承。 我會分解它。

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

所以現在知道 .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 的最基本映射位於配置文件內,該配置文件還定義了“集合”類型的映射。 這意味着一旦我將項目拆分以模擬插件架構,僅測試最簡單繼承的測試就會以新的方式失敗——無法找到基礎映射。 我所要做的就是將這些映射放入它們自己的配置文件中,並在測試中也使用該配置文件。 這只是很好的SRP

在將我自己的答案標記為已接受的答案之前,我會將我學到的知識應用到我的實際項目中。 希望我得到了它,並希望這對其他人有幫助。

有用的鏈接:

是一個很好的重構練習。 我無可否認地使用它作為構建我的示例的起點。 所以,謝謝@Olivier。

我自己在查看多態映射問題時偶然發現了這個問題。 答案是好的,但如果您想從基本映射的角度來處理它並且有許多派生類,那么這只是另一種選擇,您可以嘗試以下操作:

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

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

有關更多信息,請參閱automapper 文檔

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM