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 (aktualnie czytasz)
  4. Mapowanie SPListItem na obiekt – LINQ to SharePoint
  5. Mapowanie SPListItem na obiekt – AutoMapper
  6. Mapowanie SPListItem na obiekt – Field Mapper

W poprzednich dwóch częściach wspomniałem o tym jak przechowywać nazwy pól oraz zaprezentowałem prosty wrapper na elementy listy.

Dziś skoncentruje się na konwersji dostępnej w .NET – implicit oraz explicit.

Konwersja Implicit/Explicit do obiektu

Ogólnie zasada polega na tym, byśmy mogli prosto do instancji klasy przypisać sobie element SPListItem za pomocą znaku równości (implicit), lub za pomocą rzutowania na konkretny typ obiektu (explicit):

TestClass testClass1 = SPContext.Current.ListItem; // implicit or...

TestClass testClass2 = (TestClass)SPContext.Current.ListItem; // explicit

Implementacja takiego typu rozwiązania jest dość prosta (w przykładzie jest tylko implicit convertion, jednakże zrobienie z niego explicit to tylko podmiana jednego słowa):

public static implicit operator BankAccount(SPListItem item)
{
    if(item.ContentType.Parent.Id == new SPContentTypeId("0x01006904A5A1089E42459B0CA7DA060CC62A"))
    {
        BankAccount b = new BankAccount()
        {
            Symbol = item[SPResources.GetString("AccountFieldSymbolDisplayName")] as string,
            Bank = item[SPResources.GetString("AccountFieldBankDisplayName")] as string,
            BankAccountNumber = item[SPResources.GetString("AccountFieldTitleDisplayName")] as string,
            Currency = item[SPResources.GetString("AccountFieldCurrencyDisplayName")] as string,
            IsActive = Convert.ToBoolean(item[SPResources.GetString("AccountFieldActiveDisplayName")])
        };

        try
        {
            string orgUnitLookup = item[SPResources.GetString("AccountFieldOrgUnitDisplayName")] as string;

            if(!string.IsNullOrEmpty(orgUnitLookup))
            {
                b.OrgUnit = new SPFieldLookupValue(orgUnitLookup).LookupValue;
            }
            else
            {
                b.OrgUnit = string.Empty;
            }
        }
        catch
        {
            // jup I dont like this part as much as you
            b.OrgUnit = string.Empty;
        }

        return b;
    }
    
    return null;
}

Jak widać konwersja umożliwia nam weryfikację czy w ogólne ma nastąpić konwersja a następnie w zależności od typu danych wykonanie odpowiedniego przekształcenia na po to by można było reprezentować pole na przykład lookup jako wartość klasy.

Jednakże od razu można zauważyć pewne problemy wynikające z takiej konwersji jednakże o tym napiszę na końcu.

Sposób dostępu do obiektów na SharePoint zależy już kompletnie od was, ja zdecydowałem się przy tym rozwiązaniu niekorzystania z Repozytoriów głównie ze względu na to, że nie korzystałem z ServiceLocatora a mieć po prostu klasę tylko po to by ją mieć nie uważałem za stosowne. Cały dostęp do danych zrealizowałem, jako metody statyczne na klasie, dla przykładu metoda zwracająca jeden element:

public static BankAccount GetBankAccount(string accountNumber)
{
    SPQuery query = new SPQuery();
    query.Query = SPResources.GetString("GetActiveBankAccountByAccountNumber", accountNumber);

    SPListItemCollection items = SPContext.Current.Web.Lists[SPResources.GetString("list_accounts")].GetItems(query);

    if(items.Count == 1)
    {
        return items[0];
    }
    
    if(items.Count == 0)
    {
        return null;
    }

    // HACK: this is not a good solution, should I throw exception?
    return items[0];
}

W podobny sposób można zrealizować pobieranie listy:

public static List<BankAccount> GetBankAccounts()
{
    List<BankAccount> list = new List<BankAccount>();

    // emtpty default element
    list.Add(new BankAccount
             {
                 OrgUnit = string.Empty, 
                 Symbol = string.Empty, 
                 Bank = string.Empty, 
                 BankAccountNumber = string.Empty, 
                 Currency = string.Empty, 
                 IsActive = true
             });

    SPQuery query = new SPQuery();
    query.Query = SPResources.GetString("GetBankAccountSortedByTitleAsc");
    SPListItemCollection items = SPContext.Current.Web.Lists[SPResources.GetString("list_accounts")].GetItems(query);

    foreach(SPListItem item in items)
    {
        list.Add(item);
    }

    return list;
}

Całe rozwiązanie działa nawet dobrze w jedną stronę. Od elementu listy do instancji klasy. Zaś co kiedy chcemy zamienić klasę na element listy?

Konwersja Implicit/Explicit z obiektu

Tutaj zaczynają się schody. Po pierwsze konwersja z obiektu do SPListItem powinna zwrócić nam SPListItem, który został dodany do listy i zapewne zaktualizowany (czy aby na pewno? Może to nie jest odpowiedzialność klasy by decydować o aktualizacji tego elementu? Jeżeli klasy to czy należy dodać metodę Update by w każdej chwili móc ją wywołać? – pytania się pojawiają, ale nie znikają).

Tylko skąd możemy wiedzieć do jakiej listy dodajemy element? Korzystanie z SPContext.Current.List nie należy do najlepszych, wystarczy wywołać kod gdzie aktualną listą nie będzie lista do której chcemy dodać element, albo baa obiekt listy będzie Nullem.

Czyli albo ustalimy, w jaki sposób będzie przeprowadzana konwersja z obiektu do elementu listy albo pomijamy taką możliwość.

By móc coś takiego zrobić trzeba wiedzieć na jaką listę można dodać dany element. Jeżeli korzystamy z hardcodowanych wartośći to raczej problemu nie będzie i można takie coś łatwo zorganizować, jeżeli zaś nie korzystamy z hardcodowanych wartości a chcemy tą konfigurację gdzieś przechowywać to natrafiamy na problem Contextu, który może być Nullem, obiekt Web może nie istnieć albo wręcz przeciwnie element ma być dodany do listy na innej witrynie niż aktualnie się znajdujemy.

Takich problemów jest masa, z tego powodu ja osobiście nie zdecydowałem się na oprogramowanie konwersji w ten sposób, zamiast tego stworzyłem statyczną metodę która dodawała/aktualizowała i kasowała mi dany element z listy. Do metody przekazywałem te parametry które mnie interesowały (jak ID Site, Web, i listy).

Gdybyśmy jednak chcieli coś takiego oprogramować to można to zrobić na przykład w ten sposób:

public static implicit operator SPListItem(BankAccount bankAccount)
{
    SPList list = SPContext.Current.Web.Lists[SPResources.GetString("list_accounts")];

    SPListItem item = list.Items.Add();

    item[SPResources.GetString("AccountFieldSymbolDisplayName")] = bankAccount.Symbol;
    item[SPResources.GetString("AccountFieldBankDisplayName")] = bankAccount.Bank;
    item[SPResources.GetString("AccountFieldTitleDisplayName")] = bankAccount.BankAccountNumber;
    item[SPResources.GetString("AccountFieldCurrencyDisplayName")] = bankAccount.Currency;
    item[SPResources.GetString("AccountFieldActiveDisplayName")] = bankAccount.IsActive;
    item[SPResources.GetString("AccountFieldOrgUnitDisplayName")] = bankAccount.OrgUnitObject != null
                                                        ? new SPFieldLookupValue(bankAccount.OrgUnitObject.ItemId,
                                                                                   bankAccount.OrgUnitObject.Name)
                                                        : null;

    item["ContentTypeId"] = list.ContentTypes["BankAccount")].Id;
    item.Update();

    return item;
}

Podsumowanie

Taki sposób mapowania wykorzystałem tylko raz i jakoś nie prędko mi do tego by do niego wrócić. Nie licząc problemów z konwersją na SPListItem o czym pisałem wyżej, na pewno jest problem ze zrozumieniem kodu czytanego – skąd się biorą dane? Dlaczego brakuje wartości dla kolumny X? Jak te dane są potem konwertowane? A są? Czemu taki miszmasz jest – raz klasa raz element listy? Patrząc teraz na swój kod z przed ponad roku, zastanawiam się co mnie skłoniło do podjęcia takiej decyzji. I z tego co mogę wywnioskować z kodu (który ledwo co rozumiem), to nie licząc jednego – ale to naprawdę jednego wypadku, cała reszta danych była pobierana jako read only i ten sposób uznałem za najszybszy w realizacji. Czy był? Nie wiem ale wiem, że aplikacja działała bez zarzutu przez cały rok (i dalej działa chyba;))

Wady

  • Jest to konwersja w jedną stronę, w drugą stronę trzeba kombinować;
  • SPListItem nie zawsze musi dziedziczyć po naszym ContentType, przez co możemy przypisać taki element który nie będzie zawierał kolumn o które go zapytamy. Problem nie wystąpi w czasie kompilacji ale działania aplikacji – co w cale dobrym rozwiązaniem nie jest;
  • Trzeba zawsze iterować po wszystkich elementach zwróconych z SPQuery by przekonwertować wartości SPListItem na obiekt;
  • Wciąż jesteśmy „zmuszeni” wykorzystywać SPListItem – tak naprawdę jak patrzę na kod to widzę taki miszmasz – tu mam obiekt tam mam element to jakoś łącze to tam przekazuje i w końcu tego się czytać nie da;
  • Kod pisany jest takim miszmaszem – wygląda jak PHP, zachowuje się jak ASP.NET, a działa jak SharePoint ;)

Zalety

  • Prosta konwersja z SPListItem na obiekt umożliwia nam szybkie przekształcenie wartości na silnie typowany obiekt, dzięki czemu łatwiej będzie nim operować;
  • Można łatwo zarządzać nowymi polami (dodać kolejną linijkę w jednym miejscu);
  • Przy konwersji możemy stosować dowolne walidacje/konwersje z SPListItem na nasz obiekt, dzięki czemu jak chcemy coś wyświetlić Lookup to możemy przekonwertować go na typ powiązany i będziemy mogli kliknąć sobie . i przejść dalej do kolejnych wartośći;
  • Bardzo łatwo następuje konwersja na listy, wystarczy zrobić iterator po zwróconej kolekcji i kłopot z głowy;
  • Jest to dobry i prosty sposób na zrobienie DTO, robimy prostą konwersję z SPListItem, przekazujemy obiekt jako DTO a następnie mapujemy go sobie na to co już chcemy.

No koniec

Podoba się wam takie rozwiązanie? Stosowaliście je już kiedyś? Znajdujecie inne zalety/wady niż podałem? Zapraszam do dzielenia się przemyśleniami :)

1 KOMENTARZ

  1. Ilekroć widzę na twoim blogu wpis o sharepoincie to aż mnie ciągnie żeby na kolanach do częstochowy naginać w podzięce, że nie muszę z tym już pracować:)

Comments are closed.