Migracja z Automapper na coś niekomercyjnego

Migracja z Automapper na coś niekomercyjnego
BA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 191
0

Mam w aplikacji proste mapowania z Automapperem jak poniżej. Na co innego niekomercyjnego zmigrować takie coś (Mapperly, Mapster, a może coś innego)? Doradźcie proszę co zrobi najmniej problemów.

Kopiuj
        public AutoMapperProfile()
        {
            CreateMap<GroupAnalyticData, GroupAnalyticDataDto>();
            CreateMap<ServiceItemGroupAnalyticData, ServiceItemGroupAnalyticDataDto>();

            CreateMap<Group, GroupDto>()
                .ReverseMap();

            CreateMap<Customer, CustomerDto>()
                .ReverseMap()
                .ForMember(dest => dest.ServiceItems, opt => opt.Ignore());

            CreateMap<ServiceItem, ServiceItemDto>()
                .ReverseMap();

            CreateMap<Invoice, InvoiceDto>()
                .ReverseMap()
                .ForMember(dest => dest.InvoiceItems, opt => opt.Ignore());

            CreateMap<InvoiceItem, InvoiceItemDto>()
                .ReverseMap();

            CreateMap<ServiceItemTemplate, ServiceItemTemplateDto>()
                .ReverseMap();

            CreateMap<ServiceItemTemplateValueMapping, ServiceItemTemplateValueMappingDto>()
                .ReverseMap();

            CreateMap<Audit, AuditDto>();
        }
RJ
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 493
3

Powiem tak: nigdy nie byłem i nie zostanę fanem bibliotek mapujacych. Może jestem głupi, może nie, ale wolę to z lapy zmapowac na zasadzie że mam metodę membera w dest co sie zwie From<Cokolwiek> w dto i mapuje...

A co do zmigrowania - kup se licencje na Automappera albo wybierz swoja nową trucizne 😉

SL
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1132
2

Trochę nie widzę zastosowania takich bibliotek w sytuacjach, gdy mamy LLMy potrafiące wygenerować taki boilerplate do postaci, która jest dużo prostsza do utrzymania z uwagi na to, że nie musisz się uczyć jak działa automapper tylko po prostu poprawiasz sobie to co ci nie pasuje

Podobnie jest z wszelaką maścią wszystkich ORMów

obscurity
  • Rejestracja: dni
  • Ostatnio: dni
2

Ręczne mapowanie jest najlepsze w większości przypadków, AI autouzupełnia, masz pełną kontrolę, elastyczność i przede wszystkim wszystko jest explicit, nie ma magii, przypadkowego zmapowania czegoś czego nie chcesz lub w sposób jaki nie chcesz.

Jednak jak chcesz libkę do mapowania to najlepiej taką opartą o source generation, żeby nie zamulała refleksją i nie spowalniała aplikacji w runtime.
Polecam Mapperly https://github.com/riok/mapperly

bagietMajster
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 456
2

Jeśli już musisz to mapperly ale ja polecam ręcznie, casów gdzie to przyśpiesz programowanie to ja nie znalazłem bo zawsze jest ten jeden rodzynek, ten jeden prop który się nie zmapuje przez mapper i tak mu trzeba napisać ręcznie regułę w maperze.

somekind
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Wrocław
1

Najmniej problemów daje kompilowany kod, więc polecam Mapperly

GO
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 6
0

Zdecydowanie własny kod + pomoc AI 👍

ME
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 28
0

Ręcznie napisany extension do mapowania.

Cała reszta tylko zaciemnia kod.

somekind
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Wrocław
0
mecasog napisał(a):

Ręcznie napisany extension do mapowania.

Cała reszta tylko zaciemnia kod.

Jak to działa, że generowany extension zaciemnia kod, a taki ręcznie napisany nie?

ME
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 28
0

Prosta sprawa, wprowadza magię i dodatkowy narzut kognitywny. Mniejszy niż mapowanie za pomocą refleksji ale dalej większy niż manualnie napisany mapper.

To tak jakby w starych czasach używać szablonów T4 do generowania kodu mapującego.

Rozumiem autowygenerowany kod na wewnętrzne potrzeby jakiejś biblioteki czy w warstwie infrastrukturalnej, ale nie podoba mi się autowygenerowanie kodu który później ja muszę manualnie wywoływać.

obscurity
  • Rejestracja: dni
  • Ostatnio: dni
2
mecasog napisał(a):

Prosta sprawa, wprowadza magię i dodatkowy narzut kognitywny. Mniejszy niż mapowanie za pomocą refleksji ale dalej większy niż manualnie napisany mapper.

Rozumiem autowygenerowany kod na wewnętrzne potrzeby jakiejś biblioteki czy w warstwie infrastrukturalnej, ale nie podoba mi się autowygenerowanie kodu który później ja muszę manualnie wywoływać.

No ale to prosty kod który możesz w każdej chwili podejrzeć, dokładnie taki sam jaki byś napisał sam, ale z mniejszym ryzykiem błędu, bo robiąc ręcznie mappery często kusi żeby zrobić copy paste a potem można coś zapomnieć zmienić. Trochę magii może, ale mniej kodu to mniej błędów i mniejszy "narzut kognitywny".

ME
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 28
2
obscurity napisał(a):

Trochę magii może, ale mniej kodu to mniej błędów i mniejszy "narzut kognitywny".

xD
No na pewno magiczne generowanie mapperow to "mniejszy narzut kognitywny" niż zwykły statyczny mapper własnoręcznie napisany.

Przykład z docsow mapperly.

Najpierw trzeba nasrać partiali i atrybutów:

Kopiuj
[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
public static partial class CarMapper
{
    [MapProperty(nameof(Car.Manufacturer), nameof(CarDto.Producer))] // Map property with a different name in the target type
    public static partial CarDto MapCarToDto(Car car);
}

Który później wygeneruje taki piękny i czytelny kod "który przecież można podejrzeć":

Kopiuj
public static partial class CarMapper
    {
        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")]
        public static partial global::Riok.Mapperly.Sample.CarDto MapCarToDto(global::Riok.Mapperly.Sample.Car car)
        {
            var target = new global::Riok.Mapperly.Sample.CarDto();
            target.Name = car.Name;
            target.NumberOfSeats = car.NumberOfSeats;
            target.Color = MapToCarColorDto(car.Color);
            if (car.Manufacturer != null)
            {
                target.Producer = MapToProducerDto(car.Manufacturer);
            }
            else
            {
                target.Producer = null;
            }
            target.Tires = MapToListOfTireDto(car.Tires);
            return target;
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")]
        private static global::Riok.Mapperly.Sample.CarColorDto MapToCarColorDto(global::Riok.Mapperly.Sample.CarColor source)
        {
            return source switch
            {
                global::Riok.Mapperly.Sample.CarColor.Black => global::Riok.Mapperly.Sample.CarColorDto.Black,
                global::Riok.Mapperly.Sample.CarColor.Blue => global::Riok.Mapperly.Sample.CarColorDto.Blue,
                _ => throw new global::System.ArgumentOutOfRangeException(nameof(source), source, "The value of enum CarColor is not supported"),
            };
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")]
        private static global::Riok.Mapperly.Sample.ProducerDto MapToProducerDto(global::Riok.Mapperly.Sample.Manufacturer source)
        {
            var target = new global::Riok.Mapperly.Sample.ProducerDto(source.Id, source.Name);
            return target;
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")]
        private static global::Riok.Mapperly.Sample.TireDto MapToTireDto(global::Riok.Mapperly.Sample.Tire source)
        {
            var target = new global::Riok.Mapperly.Sample.TireDto();
            target.Description = source.Description;
            return target;
        }

        [global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")]
        private static global::System.Collections.Generic.List<global::Riok.Mapperly.Sample.TireDto> MapToListOfTireDto(global::System.Collections.Generic.IReadOnlyCollection<global::Riok.Mapperly.Sample.Tire> source)
        {
            var target = new global::System.Collections.Generic.List<global::Riok.Mapperly.Sample.TireDto>(source.Count);
            foreach (var item in source)
            {
                target.Add(MapToTireDto(item));
            }
            return target;
        }
    }

A prosty statyczny mapper mógłby wyglądać jakoś tak:

Kopiuj
using System;
using System.Linq;
using Riok.Mapperly.Sample; 

public static class CarMapper
{
    public static CarDto MapCarToDto(Car car)
    {
        return new CarDto
        {
            Name = car.Name,
            NumberOfSeats = car.NumberOfSeats,
            Color = car.Color switch
            {
                CarColor.Black => CarColorDto.Black,
                CarColor.Blue => CarColorDto.Blue,
                _ => throw new ArgumentOutOfRangeException(nameof(car.Color), car.Color, "The value of enum CarColor is not supported")
            },
            
            
            Producer = car.Manufacturer != null 
                ? new ProducerDto(car.Manufacturer.Id, car.Manufacturer.Name) 
                : null,
                
            
            Tires = car.Tires
                .Select(t => new TireDto { Description = t.Description })
                .ToList()
        };
    }
}

Do własnej oceny czytelników pozostawiam zdecydowanie która metoda ma mniejszy narzut kognitywny i jest łatwiejsza do utrzymania i debugowania.

obscurity
  • Rejestracja: dni
  • Ostatnio: dni
1

ale do kodu zaglądasz tylko w ostateczności a w codziennej pracy masz tylko

Kopiuj
[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
public static partial class CarMapper
{
    [MapProperty(nameof(Car.Manufacturer), nameof(CarDto.Producer))] // Map property with a different name in the target type
    public static partial CarDto MapCarToDto(Car car);
}

natomiast to już naciągnięty przykład bo zazwyczaj wygląda to tak:

Kopiuj
[Mapper]
public partial class CarMapper
{
    public partial CarDto CarToCarDto(Car car);
}

vs

Kopiuj
using System;
using System.Linq;
using Riok.Mapperly.Sample; 

public static class CarMapper
{
    public static CarDto MapCarToDto(Car car)
    {
        return new CarDto
        {
            Name = car.Name,
            NumberOfSeats = car.NumberOfSeats,
            Color = car.Color switch
            {
                CarColor.Black => CarColorDto.Black,
                CarColor.Blue => CarColorDto.Blue,
                _ => throw new ArgumentOutOfRangeException(nameof(car.Color), car.Color, "The value of enum CarColor is not supported")
            },
            
            
            Producer = car.Manufacturer != null 
                ? new ProducerDto(car.Manufacturer.Id, car.Manufacturer.Name) 
                : null,
                
            Tires = car.Tires
                .Select(t => new TireDto { Description = t.Description })
                .ToList()
        };
    }
}

i mapper jest już przetestowany przez twórców więc nie musisz się tym przejmować.
Do oceny czytelników pozostawiam zdecydowanie który kod jest bardziej przejrzysty i łatwiejszy do utrzymania i debugowania.

Poza tym sam w 4 poście tutaj napisałem że preferuję ręczne mapowanie, ale to tylko ma sens gdy tych encji do zmapowania masz zaledwie kilka - kilkanaście, jak jest ich setki to mapper jest już wręcz koniecznością.

Nikt też nie broni łączyć podejść i użyć generowanego mappera do prostych przypadków a ręcznie pisanego do złożonych - z tym że mapowanie tak czy inaczej nie powinno zawierać złożonej logiki

RJ
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 493
0

@obscurity: dalej bym szedł chyba w manualny mapping zeby nie miec couplingu na kolejna bibliotekę, ale kupuje argument z setką encji

ME
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 28
0
obscurity napisał(a):

natomiast to już naciągnięty przykład bo zazwyczaj wygląda to tak

Taki "naciągany" przykład podali w dokumentacji, nic nie poradzę ;)
A i nie wiem czy bym go nazwał naciąganym, bo chyba generalnie na tym polega mapowanie że mamy obiekty które różnią się strukturą i chcemy z jednego utworzyć drugi. Ten w dokumentacji i tak jest z tych prostszych bo obiekty różnią się tylko nazwami dwóch pól na najwyższym poziomie.

Natomiast większość mapperow na pewno nie kończy się na jednym atrybucie tak jak pokazujesz, no chyba że to crudzik który 1:1 do przepycha obiekty z bazy na front. Wtedy z kolei można się zastanowić czy wprowadzanie dodatkowych warstw i mapowanie ma jakikolwiek sens.

somekind
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Wrocław
0
rjakubowski napisał(a):

@obscurity: dalej bym szedł chyba w manualny mapping zeby nie miec couplingu na kolejna bibliotekę, ale kupuje argument z setką encji

Masz jakieś bonusowe punkty w pracy za nieużywanie zewnętrznych bibliotek? ;)

RJ
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 493
0
somekind napisał(a):
rjakubowski napisał(a):

@obscurity: dalej bym szedł chyba w manualny mapping zeby nie miec couplingu na kolejna bibliotekę, ale kupuje argument z setką encji

Masz jakieś bonusowe punkty w pracy za nieużywanie zewnętrznych bibliotek? ;)

Phi bonusowe punkty w pracy... 🤣

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.