Automapper Unflatten a la lista

Oct 03 2020

Tengo datos tabulares planos que necesito convertir en datos jerárquicos, y estoy tratando de hacer esto AutoMapper. Aquí hay un esquema de la fuente DTO tabular y las clases de destino maestra / detallada.

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

y los objetos de destino se ven así:

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

Mi perfil de Automapper se ve así:

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

La función que realiza la transformación:

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

Ejemplo de datos de origen:

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

El objetivo debería verse así (no en realidad Json, pero se ve mejor):

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

Unas cuantas preguntas:

  1. ¿Es esto posible con Automapper y cómo debería verse el perfil de mapeo para admitir este escenario?

  2. ¿Cómo manejaría Automapper si la fila 2 tuviera un valor de 'Cliente 2'? ¿Sobrescribir el primer valor, es decir, el último en victorias?

Respuestas

1 Stefan Oct 03 2020 at 18:51

¿Es esto posible con Automapper y cómo debería verse el perfil de mapeo para admitir este escenario?

Sí, esto es posible, pero tenga en cuenta: también AutoMapper tiene sus limitaciones. No todas las transformaciones son adecuadas para ser manejadas con AutoMapper.

Tenga en cuenta: List<OrderLines> OrderLines { get; set; }debe serpublic

No tengo todos los detalles, sí, pero básicamente puedes crear un mapa desde IEnumerable<FlatDTO>hasta Order. Trabajaré con un ejemplo.

Ejemplo

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

¿Cómo manejaría Automapper si la fila 2 tuviera un valor de 'Cliente 2'? ¿Sobrescribir el primer valor, es decir, el último en victorias?

Dependiendo de su lógica de mapeo, depende. Incluso puede realizar una verificación de estos dobles utilizando los métodos AfterMapo BeforeMap, aunque no lo hace más rápido.

Idealmente, su conjunto inicial se puede asignar a un solo pedido, por lo que contiene un solo cliente. - De lo contrario, necesitará un mapeo de IEnumerable<FlatDTO>a IEnumerable<Order>, lo que realmente está superando los límites.


Ensayador

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

Para completar la edición de Stefan; aquí hay un ejemplo de cómo puede hacerlo si necesita tener un grupo múltiple por . Al comprender el resultado esperado en su pregunta, desea agrupar los datos maestros / detallados en AcccountName y OrderNumber. Esto se puede lograr, de hecho, mapeando el IEnumerable y usando la extensión " ProjectTo " de Automapper (del espacio de nombres AutoMapper.QueryableExtensions ). En mis datos de muestra, agregué una fila con un OrderNumber diferente para ilustrar esto.

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