Refactoring: AutoMapper cz. 2

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&lt;GamerBoardGameViewModel&gt;(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&lt;BoardGame, GamerBoardGameViewModel&gt;();<br>
        //mapper 2<br>
        CreateMap&lt;Gamer, GamerBoardGameViewModel&gt;()<br>
            .ForMember(dest =&gt; dest.GamerId, opt =&gt; opt.MapFrom(src =&gt; 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&lt;Gamer, GamerBoardGameViewModel&gt;()<br>
    .ForMember(dest =&gt; dest.Id, opt =&gt; opt.Ignore());<br>

albo nawet na sztywno przypisać jakąś wartość:

<br>
CreateMap&lt;Gamer, GamerBoardGameViewModel&gt;()<br>
    .ForMember(dest =&gt; dest.Id, opt =&gt; opt.MapFrom(src =&gt; 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 =&gt;<br>
    {<br>
        cfg.CreateMap&lt;Gamer, GamerBoardGameViewModel&gt;()<br>
			.ForMember(dest =&gt; dest.Id, opt =&gt; 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 =&gt;<br>
	{<br>
		cfg.AddProfile&lt;GamerBoardGameProfile&gt;();<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 =&gt;<br>
	{<br>
		cfg.AddProfile&lt;GamerBoardGameProfile&gt;();<br>
		cfg.AddProfile&lt;GamerProfile&gt;();<br>
		cfg.AddProfile&lt;GamerBoardGameProfile&gt;();<br>
		cfg.AddProfile&lt;GameResultProfile&gt;();<br>
		cfg.AddProfile&lt;BoardGameProfile&gt;();<br>
		cfg.AddProfile&lt;GameTableProfile&gt;();<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&lt;Gamer, GamerBoardGameViewModel&gt;()<br>
		.ForMember(dest =&gt; dest.Id, opt =&gt; opt.Ignore())<br>
		.ForMember(dest =&gt; dest.GamerId, opt =&gt; opt.MapFrom(src =&gt; src.Id));<br>
}<br>

Konfiguracja błędna:

<br>
public InvalidGamerProfile()<br>
{<br>
	CreateMap&lt;Gamer, GamerBoardGameViewModel&gt;()<br>
		.ForMember(dest =&gt; dest.Id, opt =&gt; opt.MapFrom(src =&gt; src.Id)),<br>
		.ForMember(dest =&gt; dest.GamerId, opt =&gt; opt.MapFrom(src =&gt; 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&lt;BoardGame, GamerBoardGameViewModel&gt;()<br>
		.ForMember(dest =&gt; dest.BoardGameId, opt =&gt; opt.MapFrom(src =&gt; src.Id))<br>
		.ForMember(dest =&gt; dest.BoardGameName, opt =&gt; opt.MapFrom(src =&gt; src.Name))<br>
		.ForMember(dest =&gt; dest.ImageUrl, opt =&gt; opt.MapFrom(src =&gt; src.ImageUrl));<br>
}<br>

oraz z GamerProfile:

<br>
public GamerProfile()<br>
{<br>
	CreateMap&lt;Gamer, GamerBoardGameViewModel&gt;()<br>
		.ForMember(dest =&gt; dest.Id, opt =&gt; opt.Ignore())<br>
		.ForMember(dest =&gt; dest.ImageUrl, opt =&gt; opt.Ignore())<br>
		.ForMember(dest =&gt; dest.BoardGameName, opt =&gt; opt.Ignore())<br>
		.ForMember(dest =&gt; dest.GamerId, opt =&gt; opt.MapFrom(src =&gt; src.Id))<br>
		.ForMember(dest =&gt; dest.GamerNickname, opt =&gt; opt.MapFrom(src =&gt; 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.

6 uwag do wpisu “Refactoring: AutoMapper cz. 2

  1. Pingback: Refactoring: AutoMapper cz. 1 – programmer-girl

  2. ghost

    „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

    Polubienie

  3. Pingback: Refactoring: AutoMapper cz. 3 – programmer-girl

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s