Automapper Unflatten to List
Mam płaskie dane tabelaryczne, które muszę przekształcić w dane hierarchiczne i próbuję to zrobić AutoMapper. Oto szkic tabelarycznego źródła DTO i klas docelowych master / detail.
public class FlatDTO
{
public string Supplier { get; set; }
public string OrderNumber { get; set; }
public string ItemNumber { get; set; }
public string Amount { get; set; }
}
a obiekty docelowe wyglądają następująco:
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; }
}
Mój profil Automapper wygląda następująco:
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();
}
}
Funkcja wykonująca Transform:
public Order Transform (List<FlatDto> data)
{
var output = injectedFromCtorIMapper.Map<Order>(data);
//throws AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping
}
Przykładowe dane źródłowe:
Customer1 1234 Item1 200
Customer1 1234 Item2 500
Customer1 1234 Item3 4000
Cel powinien wyglądać (nie w rzeczywistości Json, ale wygląda lepiej):
Order
{
AccountName = "Customer 1",
OrderNumber = "1234",
OrderLines [
OrderLine
{
Item: "Item1",
Amount: 200
},
OrderLine
{
Item: "Item2",
Amount: 500
}
OrderLine,
{
Item: "Item3",
Amount: 4000
}
]
}
Kilka pytań:
Czy jest to możliwe w przypadku Automapper i jak powinien wyglądać profil mapowania, aby obsługiwał ten scenariusz?
Jak poradziłby sobie Automapper, gdyby wiersz 2 miał wartość „Klient 2”? Zastąpić pierwszą wartość, czyli ostatnią w wygranych?
Odpowiedzi
Czy jest to możliwe w przypadku Automapper i jak powinien wyglądać profil mapowania, aby obsługiwał ten scenariusz?
Tak, jest to możliwe - ale pamiętaj: również AutoMapper ma swoje ograniczenia. Nie wszystkie transformacje są przystosowane do obsługi przez AutoMapper.
Uwaga: List<OrderLines> OrderLines { get; set; }musi byćpublic
Nie mam wszystkich szczegółów tak, ale w zasadzie możesz stworzyć mapę od IEnumerable<FlatDTO>do Order. Opracuję przykład.
Przykład
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,
})));
Jak poradziłby sobie Automapper, gdyby wiersz 2 miał wartość „Klient 2”? Zastąpić pierwszą wartość, czyli ostatnią w wygranych?
Zależy to od logiki mapowania. Możesz nawet sprawdzić te dublety za pomocą metod AfterMaplub BeforeMap- nie przyspiesza to jednak.
W idealnym przypadku początkowy zestaw można zmapować do pojedynczego zamówienia, a zatem zawierać jednego klienta. - W przeciwnym razie będziesz potrzebować mapowania od IEnumerable<FlatDTO>do IEnumerable<Order>- co naprawdę przesuwa granice.
Próbnik
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);
}
Aby zakończyć edycję Stefana; oto przykład, jak możesz to zrobić, jeśli potrzebujesz wielu grup według . Rozumiejąc oczekiwany wynik pytania, chcesz zgrupować dane główne / szczegółowe w AcccountName i OrderNumber. Można to osiągnąć poprzez mapowanie IEnumerable i użycie rozszerzenia „ ProjectTo ” automappera (z przestrzeni nazw AutoMapper.QueryableExtensions ). W moich przykładowych danych dodałem jeden wiersz z innym numerem zamówienia, aby to zilustrować.
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);
}
}
}