Dzisiaj czas na kontynuację poprzedniego wpisu o AutoMapperze.
Miałam następujący problem: moje mappery działały, aczkolwiek nie umiałam użyć 2 różnych obiektów początkowych (source), żeby zmapować je do jednego końcowego obiektu (destination). Doszłam do tego, że nie można zrobić tego w jednym kroku. Trzeba najpierw zmapować na podstawie jednego obiektu i wynik tego mapowania zmapować na podstawie drugiego obiektu. Kod wywołania wyglądał tak:
<br> //source 1 - boardGame<br> //source 2 - gamer<br> //destination - gamerBoardGameViewModel<br> var gamerBoardGameViewModel = Mapper.Map<GamerBoardGameViewModel>(boardGame);<br> Mapper.Map(gamer, gamerBoardGameViewModel);<br>
a kod profilu wyglądał tak:
<br> public class GamerBoardGameProfile : Profile<br> {<br> public GamerBoardGameProfile()<br> {<br> //mapper 1<br> CreateMap<BoardGame, GamerBoardGameViewModel>();<br> //mapper 2<br> CreateMap<Gamer, GamerBoardGameViewModel>()<br> .ForMember(dest => dest.GamerId, opt => opt.MapFrom(src => src.Id));<br> }<br> }<br>
Error mapping types Property: Id
Cokolwiek zrobiłam, zawsze wyskakiwał mi powyższy błąd. Po kilkudziesięciu minutach poszukiwań doszłam do tego, że mapper mapował Id (typu string) z obiektu Gamer, na Id (typu int) z obiektu GamerBoardGameViewModel. Typy się nie zgadzały, stąd powyższy błąd. Czemu tak robił? Ponieważ AutoMapper sam mapuje propercje, które mają takie same nazwy (w moim przypadku Id). Próbowałam więc zignorować mapowanie tej propercji przez wywołanie metody Ignore():
<br> CreateMap<Gamer, GamerBoardGameViewModel>()<br> .ForMember(dest => dest.Id, opt => opt.Ignore());<br>
albo nawet na sztywno przypisać jakąś wartość:
<br> CreateMap<Gamer, GamerBoardGameViewModel>()<br> .ForMember(dest => dest.Id, opt => opt.MapFrom(src => 1));<br>
Niestety nic to nie dało.
Zaczynamy od nowa
Zakomentowałam wszystkie mapery i zaczęłam rozwiązywanie problemu od nowa, w trochę inny sposób. Najpierw dodałam konfigurację mapowania wewnątrz metody Mapper.Initialize():
<br> public static void InitializeAutoMapper()<br> {<br> Mapper.Initialize(cfg =><br> {<br> cfg.CreateMap<Gamer, GamerBoardGameViewModel>()<br> .ForMember(dest => dest.Id, opt => opt.Ignore());<br> });<br> }<br>
Przetestowałam to i szok, zadziałało. Super! Przeniosłam tę konfigurację z powrotem do Profilu i wywołałam go w Mapper.Initialize():
<br> public static void InitializeAutoMapper()<br> {<br> Mapper.Initialize(cfg =><br> {<br> cfg.AddProfile<GamerBoardGameProfile>();<br> });<br> }<br>
Nadal działało. Odkomentowałam pozostałem Profile i… I NIE DZIAŁA.
<br> public static void InitializeAutoMapper()<br> {<br> Mapper.Initialize(cfg =><br> {<br> cfg.AddProfile<GamerBoardGameProfile>();<br> cfg.AddProfile<GamerProfile>();<br> cfg.AddProfile<GamerBoardGameProfile>();<br> cfg.AddProfile<GameResultProfile>();<br> cfg.AddProfile<BoardGameProfile>();<br> cfg.AddProfile<GameTableProfile>();<br> });<br> }<br>
Ale przecież działało!
No ale jak to? Jeden działał, a wszystkie już nie? Jak to możliwe? No i tutaj właśnie zapaliła się żaróweczka nad głową. Wystarczyło sprawdzić dokładnie mapery żeby zauważyć, że w dwóch miejscach miałam taki sam maper (z obiektu X na obiekt Y). Co z tego, że pierwsza konfiguracja mapera była prawidłowa. Podczas deklaracji AutoMappera ta konfiguracja była prawdopodobnie zastępowana przez kolejną (która była już błędna).
Kofiguracja prawidłowa:
<br> public ValidGamerProfile()<br> {<br> CreateMap<Gamer, GamerBoardGameViewModel>()<br> .ForMember(dest => dest.Id, opt => opt.Ignore())<br> .ForMember(dest => dest.GamerId, opt => opt.MapFrom(src => src.Id));<br> }<br>
Konfiguracja błędna:
<br> public InvalidGamerProfile()<br> {<br> CreateMap<Gamer, GamerBoardGameViewModel>()<br> .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)),<br> .ForMember(dest => dest.GamerId, opt => opt.MapFrom(src => src.Id));<br> }<br>
Wniosek
Dobrym rozwiązaniem, który pozwoli na niepowtarzanie opisanego błędu, jest podział maperów ze względu na używany source. Pozwoli to na uniknięcie niepotrzebnych duplikacji i zmarnowanych godzin. Poniżej kod używany do mapowania do obiektu typu GamerBoardGameViewModel pochodzący z BoardGameProfile (wywoływanego najpierw):
<br> public BoardGameProfile()<br> {<br> CreateMap<BoardGame, GamerBoardGameViewModel>()<br> .ForMember(dest => dest.BoardGameId, opt => opt.MapFrom(src => src.Id))<br> .ForMember(dest => dest.BoardGameName, opt => opt.MapFrom(src => src.Name))<br> .ForMember(dest => dest.ImageUrl, opt => opt.MapFrom(src => src.ImageUrl));<br> }<br>
oraz z GamerProfile:
<br> public GamerProfile()<br> {<br> CreateMap<Gamer, GamerBoardGameViewModel>()<br> .ForMember(dest => dest.Id, opt => opt.Ignore())<br> .ForMember(dest => dest.ImageUrl, opt => opt.Ignore())<br> .ForMember(dest => dest.BoardGameName, opt => opt.Ignore())<br> .ForMember(dest => dest.GamerId, opt => opt.MapFrom(src => src.Id))<br> .ForMember(dest => dest.GamerNickname, opt => opt.MapFrom(src => src.Nickname));<br> }<br>
Podoba Ci się to, co tworzę? Chcesz dostawać informacje o:
– wydarzeniach, które organizuję lub wspieram (np. konferencje, meetupy, webinary)
– inicjatywach, które organizuję lub wspieram (np. GeekWeekWro, DevAdventCalendar)
– moich prelekcjach, kursach i szkoleniach
– wyróżnionych artykułach z mojego bloga
0% SPAMu, 100% informacji! Krótko i na temat.
Pingback: Refactoring: AutoMapper cz. 1 – programmer-girl
Jesli chcesz duzo ignorowac to proponuje zrobic extension method (o wiele ladniej sie czyta):
https://gist.github.com/MaLiN2223/990e42299dc7790c02f91179c9715c96 analogicznie przy duzej liczbie mapowanych pol przydaje sie Define 🙂
W linku znajduje sie tez metoda do mapowania T na T?
PolubieniePolubione przez 1 osoba
O dziękuję bardzo! Dobry kawałek kodu 🙂
PolubieniePolubienie
„Czemu tak robił? Ponieważ AutoMapper sam mapuje propercje, które mają takie same nazwy (w moim przypadku Id). ” Nie do końca tak jest. AutoMapper domyślnie mapuje property używając „StartsWith” co znaczy mniej więcej tyle: mamy klasę A a w niej propertę „Test” oraz klasę B a w niej propertę „TestKolejny” – automapper będzię próbował mapować i może nawet mu się to udać – exception poleci jeśli mamy różne typy i powiedzmy chcemy mapować null do nie nullowalnego obiektu.
Natomiast mapowanie „ATakiTest” na „TestKolejny” nigdy nie będzię wykonywane. Polecam sprawdzić organoleptycznie na najnowszej wersji AutoMappera
PolubieniePolubienie
Ooo dzieki! Dobrze wiedzieć.
PolubieniePolubienie
Pingback: Refactoring: AutoMapper cz. 3 – programmer-girl