Automapper Unflatten to List

Oct 03 2020

階層データに変換する必要のあるフラットな表形式のデータがあり、これをで実行しようとしていAutoMapperます。これは、表形式のDTOソースとマスター/詳細宛先クラスのスケッチです。

public class FlatDTO
{
    public string Supplier { get; set; }
    public string OrderNumber { get; set; }
    public string ItemNumber { get; set; }
    public string Amount { get; set; }
}

宛先オブジェクトは次のようになります。

public class Order
{
    public string AccountName { get; set; }
    public string OrderNumber { get; set; }
    List<OrderLines> OrderLines { get; set; }
}

public class OrderLines 
{
    public string Item { get; set; }
    public string Amount { get; set; }
}

私のオートマッパープロファイルは次のようになります。

public class MyAutomapperProfile : Profile
{
    public MyAutomaperProfile ()
    {
        CreateMap<FlatDto, Order>()
          .ForMember(des => des.Account, opt => opt.MapFrom(src => src.Supplier))
          .ReverseMap();

        CreateMap<FlatDto, OrderLines>()
          .ForMember(des => des.Item, opt => opt.MapFrom(src => src.Item))
          .ReverseMap();
    }
}

変換を実行する関数:

public Order Transform (List<FlatDto> data)
{
     var output = injectedFromCtorIMapper.Map<Order>(data);
     //throws AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping 
}

ソースデータの例:

Customer1    1234    Item1   200
Customer1    1234    Item2   500
Customer1    1234    Item3   4000

ターゲットは次のようになります(実際にはJsonではありませんが、見栄えが良くなります):

Order
{ 
    AccountName = "Customer 1",
    OrderNumber = "1234",
    OrderLines [
                   OrderLine
                   {
                      Item: "Item1",
                      Amount: 200
                   },
                   OrderLine
                   {
                      Item: "Item2",
                      Amount: 500
                   }
                   OrderLine,
                   {
                      Item: "Item3",
                      Amount: 4000
                   }
                ]
}

いくつかの質問:

  1. これはAutomapperで可能ですか?このシナリオをサポートするためにマッピングプロファイルはどのように見えるべきですか?

  2. 行2の値が「Customer2」の場合、Automapperはどのように処理しますか?最初の値、つまり勝利の最後の値を上書きしますか?

回答

1 Stefan Oct 03 2020 at 18:51

これはAutomapperで可能ですか?このシナリオをサポートするためにマッピングプロファイルはどのように見えるべきですか?

はい、これは可能ですが、注意してください。AutoMapperにも制限があります。すべての変換がAutoMapperでの処理に適しているわけではありません。

ノートの操作を行います。List<OrderLines> OrderLines { get; set; }でなければなりませんpublic

私はすべての詳細を持っているわけではありませんが、基本的にはからIEnumerable<FlatDTO>へのマップを作成できますOrder。例を挙げます。

例

CreateMap<IEnumerable<FlatDTO>, Order>()
   .ForMember(d => d.AccountName, o => o.MapFrom(s=> s.Select(c => c.Supplier).FirstOrDefault()))
   .ForMember(d => d.OrderLines, o => o.MapFrom(s=> s.Select(c => new OrderLines()
       {
           Amount = c.Amount,
           Item = c.ItemNumber,
       })));

行2の値が「Customer2」の場合、Automapperはどのように処理しますか?最初の値、つまり勝利の最後の値を上書きしますか?

マッピングロジックによって異なります。AfterMapまたはBeforeMapメソッドを使用して、これらのダブルのチェックを実行することもできますが、高速にはなりません。

理想的には、初期セットを単一の注文にマッピングして、単一の顧客を含めることができます。-それ以外の場合は、からIEnumerable<FlatDTO>へのマッピングが必要になりますIEnumerable<Order>-これは実際に限界を押し上げています。


テスター

static void Main(string[] args)
{
    var dtos = new[]
    {
       new FlatDTO() {Supplier = "you", Amount = "10", ItemNumber = "1", OrderNumber = "123"},
       new FlatDTO() {Supplier = "you", Amount = "12", ItemNumber = "2", OrderNumber = "234"}
    };

    var config = new MapperConfiguration(cfg => cfg.AddProfile<MyAutomapperProfile>());
    var mapper = config.CreateMapper();

    var order = mapper.Map<IEnumerable<FlatDTO>, Order>(dtos);
}
2 Reno Oct 03 2020 at 19:42

ステファンの編集を完了するには; で複数のグループを作成する必要がある場合の方法の例を次に示します。質問で期待される結果を理解し、AcccountNameとOrderNumberのマスター/詳細データをグループ化します。これは、実際、IEnumerableをマッピングし、Automapperの「ProjectTo」拡張機能(名前空間AutoMapper.QueryableExtensionsから)を使用することで実現できます。サンプルデータでは、これを説明するために、異なるOrderNumberを持つ1つの行を追加しました。

using AutoMapper;
using AutoMapper.QueryableExtensions;
using System.Collections.Generic;
using System.Linq;

namespace AutomapperUnflatten
{
    public class FlatDTO
    {
        public string Supplier { get; set; }
        public string OrderNumber { get; set; }
        public string ItemNumber { get; set; }
        public string Amount { get; set; }
    }

    public class Order
    {
        public string AccountName { get; set; }
        public string OrderNumber { get; set; }
        public List<OrderLines> OrderLines { get; set; }
    }

    public class OrderLines
    {
        public string Item { get; set; }
        public string Amount { get; set; }
    }


    class Program
    {
        static void Main(string[] args)
        {            
            var configuration = new MapperConfiguration(cfg => {
                cfg.CreateMap<IEnumerable<FlatDTO>, Order>()
                     .ForMember(d => d.AccountName, opt => opt.MapFrom(src => src.FirstOrDefault().Supplier))
                     .ForMember(d => d.OrderNumber, opt => opt.MapFrom(src => src.FirstOrDefault().OrderNumber))
                     .ForMember(d => d.OrderLines, opt => opt.MapFrom(src =>
                         src.Select(s => new OrderLines() { Item = s.ItemNumber, Amount = s.Amount })));
                     });


            var inputData = new List<FlatDTO>()
            {
                new FlatDTO(){ Supplier = "Customer1", OrderNumber = "1234", ItemNumber = "Item1", Amount = "200"},
                new FlatDTO(){ Supplier = "Customer1", OrderNumber = "1234", ItemNumber = "Item1", Amount = "500"},
                new FlatDTO(){ Supplier = "Customer1", OrderNumber = "1234", ItemNumber = "Item1", Amount = "4000"},
                new FlatDTO(){ Supplier = "Customer1", OrderNumber = "9999", ItemNumber = "Item1", Amount = "4000"},
            };
            

            var result = inputData.GroupBy(c => (c.Supplier, c.OrderNumber)).Select(f => f).AsQueryable().ProjectTo<Order>(configuration);            
        }
    }
}