Może zainteresują Cię pozostałe posty z cyklu Mapowanie SPListItem na obiekt:

  1. Mapowanie SPListItem na obiekt – Wprowadzenie
  2. Mapowanie SPListItem na obiekt – Wrapper
  3. Mapowanie SPListItem na obiekt – implicit/explicit conversion
  4. Mapowanie SPListItem na obiekt – LINQ to SharePoint
  5. Mapowanie SPListItem na obiekt – AutoMapper (aktualnie czytasz)
  6. Mapowanie SPListItem na obiekt – Field Mapper

Z tym typem mapowania miałem najwięcej bolączki – jest on naprawdę najgorszym z jakim przyszło mi pracować i głównie zastosowałem go tylko raz by sprawdzić czy się opłaca. Post ten więc raczej jest ku przestrodze a nie opisem sposobu mapowania obiektów.

AutoMapper to bardzo przydatne narzędzie, o którym w sieci jest już głośno a Procent poświęcił mu już pięć postów.

Nie pamiętam jak dawno temu zastanawiałem się nad tym jak to ugryźć w odniesieniu do SharePointa, jednakże napotkałem na problemy o których warto wspomnieć.

Po pierwsze AutoMapper jest narzędziem do mapowania Obiektu na Obiekt a nie Metadanych obiektu na Obiekt. Pod meta danymi mam namyśli dostęp do pól poprzez indeksacje czy to po ID czy po nazwie pola.

Po drugie dla każdej klasy trzeba tworzyć osobny konwerter. Oczywiście daje to nam możliwość rozdzielenia odpowiedzialności i możliwość samemu określenia co i jak ma być konwertowane. Mimo to na początku musimy napisać własną klasę, potem jeszcze klasę konwertującą a potem pamiętać o tym by zawsze używać odpowiedniego konwertera. Mamy coraz więcej roboty – zamiast mniej, przecież tylko o to nam w tych mapowaniach chodzi, by nie pisać zbędnego kodu :) Oczywiście w pierwszej i drugiej części cyklu wspomniałem o generatorach, które można samemu napisać lub wykorzystać już istniejące, dzięki czemu pozostanie napisanie jedynie konwertera (co też da się zautomatyzować).

Po trzecie przy wykorzystaniu AutoMappera, natrafiamy na problem z aktualizacją danych, dla których znów musimy pisać własny konwerter a do tego by to zrobić trzeba na liście najpierw dodać element, a po mapowaniu zaktualizować go.

Po czwarte, tak naprawdę ciągle musimy operować SPListItem, mapper działa na instancji więc musimy pobrać sobie SPListItem (jak?) a dopiero potem z mapować, to samo tyczy się w drugą stronę, najpierw stworzyć, potem z mapować. W końcu kod będzie wyglądał jak w PHP argh! :)

Po piąte natrafiamy na problem zarządzania instancjami SPWeb, SPSite i śledzenia IDkó list, stron i wszystkiego czego potrzebujemy by móc dodać/pobrać element ze strony.

Po szóste i na pewno nie ostatnie, to, że AutoMapper nie umożliwia nam wykonywania zapytań na kolekcjach obiektów, zaś konwertowanie wszystkich elementów listy do listy obiektów by móc na niej wykonać select nie jest rozwiązaniem, które tygryski lubią najbardziej. Można pomyśleć zaś o jakimś repozytorium które udostępni odpowiednie metody, ale to znów zwrócona kolekcja będzie musiała być przekonwertowana za pomocą refleksji. Znając szybkość działania SharePoint takie rzeczy mogą tylko spowodować, że nasza aplikacja nie będzie już taka szybka jakby tego klient chciał. W szczególność, że coraz częściej spotykam się z wymaganiami, że czas oczekiwania użytkownika na dane nie może być dłuższy niż X sekund. Co jak wiemy nie zawsze jest do spełnienia :)

Oczywiście można pomyśleć o jakiejś pięknej architekturze, opakowaniu tego w klasy, fabryki, repozytoria, usługi czy cokolwiek i nasz kod może nawet elegancko wyglądać. Jednak czy jest to warte zachodu? Moim zdaniem nie. Także IMO po co na siłę wykorzystywać mapper, który tak naprawdę przeznaczony jest dla innych celów?

Dla przykładu, weźmy prostą klasę TestList:

class TestList
{
    public int Id { get; set; }
    public string Title { get; set; }
}

Teraz jeżeli chcemy za pomocą AutoMappera uzyskać ją z obiektu SPListItem to musimy stworzyć konwerter:

class ItemToTestListConverter : ITypeConverter<SPListItem, TestList>
{
    public TestList Convert(ResolutionContext context)
    {
        var item = (SPListItem)context.SourceValue;

        TestList t = new TestList();
        t.Title = item.Title;
        t.Id = item.ID;

        return t;
    }
}

W drugą stronę, kiedy chcemy z obiektu uzyskać SPListItem, piszemy kolejny konwerter:

class TestListToItemConverter : ITypeConverter<TestList, SPListItem>
{
    public SPListItem Convert(ResolutionContext context)
    {
        var item = (TestList)context.SourceValue;
        var dest = (SPListItem)context.DestinationValue;

        dest["Title"] = item.Title;
        dest.Update();

        // przydaje sie posiadac ID
        item.Id = dest.ID;

        return dest;
    }
}

Teraz możemy skonfigurować AutoMappera:

Mapper.CreateMap<SPListItem, TestList>().ConvertUsing<ItemToTestListConverter>();
Mapper.CreateMap<TestList, SPListItem>().ConvertUsing<TestListToItemConverter>();

I tak możemy to wszystko razem wykorzystać:

using System;
using AutoMapper;
using Microsoft.SharePoint;

namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            // konfigurujemy AutoMappera
            Mapper.CreateMap<SPListItem, TestList>().ConvertUsing<ItemToTestListConverter>();
            Mapper.CreateMap<TestList, SPListItem>().ConvertUsing<TestListToItemConverter>();

            using(SPSite site = new SPSite("http://gutek-pc"))
            {
                using(SPWeb web = site.OpenWeb())
                {
                    // Pobieramy liste i element
                    SPList list = web.Lists["Test"];
                    SPListItem item = list.Items[0];

                    // konwertujemy element
                    var testItem = Mapper.Map<SPListItem, TestList>(item);
                    Console.WriteLine(testItem.Title);

                    // pomijamy ID z wiadomych przyczyn
                    TestList newItem = new TestList {Title = "Nowy element"};

                    // dodajemy nowy element
                    SPListItem mappedItem = Mapper.Map<TestList, SPListItem>(newItem, list.Items.Add());
                    
                    // ID z mapowanego obiektu (dziala)
                    Console.WriteLine(newItem.Id);
                    Console.ReadLine();
                }
            }
        }
    }
}

A wyniki wyglądają tak:

console_result

page_result

Nie będę się już tutaj rozpisywał nad zaletami i wadami rozwiązania, cały post prawie jest o wadach :) jedynym miejscem kiedy widzę zastosowanie AutoMappera to wtedy kiedy do istniejącego projektu dochodzi potrzeba zrobienia mapowania na obiekt i wysłania go do systemy XYZ, który następnie obsłuży sobie odpowiednio to co dostanie. Oczywiście są lepsze rozwiązania niż mapowanie jak na przykład udostępnienie Custom Web Service, jednakże AutoMapper wydaje się w tym wypadku dobrym sposobem na przeprowadzenie konwersji, która będzie można potem dość „łatwo” zarządzać.

Tak z ciekawości, ktoś z was próbował wykorzystać mapowanie SPListItem na obiekt z wykorzystaniem AutoMappera? Jakieś spostrzeżenia? Dzięki :)

2 KOMENTARZE

Comments are closed.