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

  1. Mapowanie SPListItem na obiekt – Wprowadzenie (aktualnie czytasz)
  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
  6. Mapowanie SPListItem na obiekt – Field Mapper

Pora rozpocząć pierwszy cykl na blogu, a że odkąd pisze rozwiązania w oparciu o SharePoint zawsze natrafiam na ten sam problem przy rozpoczęciu projektu – mapować czy nie mapować. Za każdym razem decyzja jest inna, do tego stopnia inna, że postanowiłem opisać w kilku postach sposoby, które wykorzystuje/wykorzystywałem i podzielić się swoimi spostrzeżeniami.

Kilka słów wstępu

SharePoint jest dość specyficznym rozwiązaniem, gdzie do jednej „kolumny” można się dobrać na cztery różne sposoby zaś do pola na trzy. Problem polega na tym iż nie ma możliwości wykorzystania tylko i wyłącznie jednego ze sposobów w dużych i skomplikowanych rozwiązaniach – jest to spowodowane ograniczeniami jakie narzucają pliki XML od SharePoint. Jednym z problemów jest tworzenie pól typu SPFieldLookup. Istnieje kilka obejść problemu jednak przeważnie wymagają one nie tylko utworzenia Content Type ale także definicji listy czy też nawet definicji strony, co IMO komplikuje sprawę jeszcze bardziej, dlatego odwołuje się do tego jako nie da w dalszej części postu.

Dlaczego w ogóle mówię o sposobie dostępu do kolumny? Z dwóch powodów, należy dobrać taki sposób by:

  1. Był prosty do auto-aktualizacji – w trakcie wytwarzania oprogramowania, nasze pola mogą ulegać zmianie, mogą dochodzić nowe a stare mogą być usuwane, a nikt nie chce tym zarządzać ręcznie;
  2. Dał nam możliwość nie tylko dostępu do wartości kolumny, ale także umożliwiał dostanie się bezpośrednio do pola odpowiadającego za wartość kolumny – po co pisać dwa/trzy różne sposoby dostępu do różnych danych, skoro dzielą one w większości te same właściwości?

Zaczynamy więc przegląd dostępnych możliwości oraz plusy i minusy danych rozwiązań.

Dostęp poprzez Indeks

Jeden z chyba najgorszych jaki można spotkać :) Polega on na tym iż do wartości w elemencie odwołujemy się za pomocą indeksu kolumny:

SPContext.Current.ListItem[0];

Po pierwsze sposób ten jest bardzo ciężko utrzymać – wystarczy, że zmienimy kolejność kolumn to już w całym projekcie gdzie występuje 4 trzeba zamienić ją na 5. Dodatkowo kolejność może ulec zmianie na stronie, na co już wpływu nie mamy. Nie ma co się więcej na ten temat rozpisywać. Raczej z tego korzystać nie będziemy (przynajmniej ja nie będę) – nie spełnia on pierwszego z punktów opisanych w wprowadzeniu. Drugi zaś jest spełniony – indeks kolumny w elemencie jest taki sam jak indeks danego pola w SharePoint, czyli jeden sposób dwie wartości, ql! :)

Dostęp poprzez Display Name i Internal Name

Oba dostępy zostały połączone w jeden punkt jako, że SharePoint traktuje je tak samo. Mianowicie, jeżeli odpytamy element o zwrócenie nam wartości kolumny MojaKolumna to SharePoint na początku wykona zapytanie z prośbą o zwrócenie pola o danym internal name i jeżeli pole nie zostanie zwrócone dopiero wtedy wykonuje zapytanie o pole z taką nazwą (display name).

Można więc od razu zaobserwować pewien szczególny przypadek: nazwa internal name kolumny jest taka sama jak nazwa display name innej kolumny. Próbując się odwołać do pola MojaKolumna zawsze zostanie zwrócona ta pierwsza. Dodatkowo, internal name != display name, przez co możemy mieć wiele kolumn o takiej samej wartości display name a różniącymi się jedynie poprzez internal name. Dla przykładu MojaKolumna, może mieć wartość internal name raz MojaKolumna raz MojaKolumna0, zaś display name taki sam.

Poza wyżej wymienionymi problemami, jest jeszcze jeden, dość istotny. W rozwiązaniach wielojęzycznych display name może mieć różne nazwy. Przez co na przykład odwołanie do MyColumn dla polskiego SPS, zakończy się wyjątkiem, gdyż na polskim WSS nazwa może być MojaKolumna.

Innym minusem jest to iż raz w indeksacji możemy odwołać się za pomocą display name i internal name a raz jedynie za pomocą display name – przykład SPFieldCollection, choć jest metoda GetFieldByInternalName, to nie jest ona używana w indeksacji kolekcji pól. Czyli trzeba o tym pamiętać (a wierzcie mi, można zapomnieć), że internal name stosujemy na SPListItem zaś display name do SPFieldCollection.

SPContext.Current.ListItem[“_MyInternalName”];
SPContext.Current.ListItem.Fields.GetFieldByInternalName(“_MyInternalName”);

Dodatkowo spotkałem się z sytuacją kiedy dwa pola, o tym samym internal name istniały w SharePoint, wgrane przez feature zaś dwóch tych samych Guidów (poniżej) nie widziałem. Przy tym jak miałem pola o tych samych internal name to działało to pierwsze pole, zaś drugie (nowo wgrane) świrowało (ciężko to inaczej nazwać).

Od tamtej pory zacząłem stosować pewną konwencję nazewnictwa internal name: _[Typ|Lista]NazwaPola, na przykład _InvoiceDocumentNo. Daje mi to pewność iż nie powtórzę nazwy (przynajmniej do póki racjonalnie myślę i piszę co o 4 nad ranem różnie wypada).

Dostęp poprzez GUID

Jest to chyba jeden z najbezpieczniejszych i najpewniejszych dostępów do wartości elementu czy też do pola w SharePoint. Mamy gwarancję, że:

  1. GUID jest unikatowy;
  2. GUID pola jest taki sam jak GUID wartości w kolumnie.

Teraz mimo iż jest to najlepszy sposób dostępu do elementu to zarazem jedyny, który tak naprawdę wymaga utworzenia naszych własnych kolumn dla rozwiązania w XML. Dzieje się tak, gdyż przy tworzeniu ręcznym kolumny (z interfejsu lub z kodu) GUID jest za nas generowany. I albo zakładamy, że będziemy działać na danym, nie zmienionym środowisku (sic!), albo zaczynamy myśleć o bardziej uniwersalnym rozwiązaniu. W wypadku pierwszym, jeżeli naprawdę chcemy tworzyć ręcznie kolumny to warto zastosować internal name (tworzymy kolumnę najpierw z internal name a następnie modyfikujemy i zmieniamy jej nazwę na display name, dzięki czemu uzyskamy taką wewnętrzną nazwę jaką chcemy). W drugim przypadku spędzamy czas na pisaniu własnych kolumn w XMLu.

Minusem GUIDa, jest pole SPFieldLookup, które jak już we wstępie zaznaczyłem nie da się utworzyć za pomocą XMLa i jest to przeważnie robione bezpośrednio z kodu – czyli nasz GUID jest generowany :( W takich wypadkach należało by skorzystać z internal name. We wszystkich innych GUID jest The Best :)

Dodatkowo, SharePoint udostępnia nam klasę ze wszystkimi GUIDami wbudowanych pól, SPBuiltInFieldId. Więc jesteśmy już do przodu, teraz jedynie brakuje nam naszych własnych GUIDów :) (no i internal name dla pól lookup).

Brak mapowania

Przeszliśmy po wszystkich typach dostępu do danych, ze względu na to, iż prawie każdy system mapowania o jakim będę wspominał będzie musiał z tych wartości korzystać. Warto więc zapoznać się który sposób dostępu jest najlepszy. Moim osobistym wyborem od kilku projektów jest GUID dla wszystkich pól, zaś internal name dla pól SPFieldLookup. Do takiego też zachęcam was, jeżeli jednak uważacie iż inny będzie bardziej odpowiedni to śmiało – pamiętajcie tylko o skali projektu, chodzi o to byście po dwóch miesiącach nie chcieli sobie podciąć żył krakersem, gdyż jedna zmiana będzie wymagała X poprawek.

Dobra, sekcja nazywa się brak mapowania, dlaczego (uwielbiam to pytanie)? Dlatego, że… 90% przypadkach mapowania nie stosujemy a jedynie odwołujemy się po indeksacji SPListItem do wartości elementu, dodatkowo w większości sposobów mapowania wciąż będziemy to robić. Warto więc poznać sposoby przechowywania wartości, które nas interesują :) Podczas pracy z SharePoint przeszedłem chyba przez wszystkie sposoby przechowywania nazwy/Guidów jakie można sobie wymyśleć i mieć do nich w miarę prosty dostęp z kodu:

  1. Całkowity brak trzymania danych wartości, to znaczy, za każdym razem podawałem wartość ciągu znaków;
  2. Trzymanie nazw/Guidów w satellite assembly – przy tworzeniu rozwiązań wielojęzycznych było to naprawdę dobre rozwiązanie, minusem było pamiętanie nazwy klucza zasobu, i znów podawanie go jako ciąg znaków;
  3. Trzymanie nazw/Guidów w zasobie lokalnym – różnica pomiędzy 2 polega na tym, iż plik resx był w tym samym assembly co kod, więc miałem silnie typowane zasoby;
  4. Trzymanie nazw/Guidów w satellite assembly z atrybutem InternalsVisibleTo – to samo co 2, z możliwością dostępu jak w 3;
  5. Trzymanie nazw/Guidów w klasie – tutaj wiele wariancji, trzymanie tego w lokalnej klasie w której aktualnie piszę lub w globalnej statycznej itp. itd. aż do przechowywania wartości w metodach (sic!).
SPContext.Current.ListItem[“_MyInternalName”]; // 1
SPContext.Current.ListItem[ResourcesImpl.GetString("MyInternalName", ResType.Fields)]; // 2
SPContext.Current.ListItem[FieldsRes.MyInternalName]; // 3
SPContext.Current.ListItem[MySatAss.FieldsRes.MyInternalName]; // 4
SPContext.Current.ListItem[MyFields.MyInternalName]; // 5

Widać, że próbowałem wszystkiego :) Ostatecznie wybrałem opcję piątą, która zarazem była także pierwszą jaką próbowałem a dokładniej próbowaliśmy w projekcie IT Core. Dzięki trzymaniu tego w klasach, możemy nie tylko zmieniać wartości na te które nas interesują ale także nazwy zmiennych, a VS nam pomoże z refaktorować kod. W przypadku ciągu znaków, jedynie ctrl+h pomagało, zaś w zasobach… różnie bywało, raz ctrl+h raz podmiana ręczna, dużo zachodu na nic. Dodatkowym atutem klas jest to, iż możemy je bardzo łatwo wygenerować. W sieci można znaleźć nie jeden szablon T4, CodeSmith czy aplikację, która nam to umożliwia:

Dobra napisałem, że można a znalazłem tylko rozwiązania Imtech :) sorki, ale jeżeli znajdziecie inne to dajcie znać.

Dzięki automatyzacji nie musimy się przejmować utrzymaniem wartości, jest to pewien plus. Jednakże do innych automatyzacji dojdziemy w następnych częściach cyklu. Sądzę, że na wprowadzenie, temat został wyczerpany, jeżeli macie jakiś pytania, sugestię, proszę dajcie znać w komentarzach, dzięki.

3 KOMENTARZE

  1. Hej,
    Po twoim poście chyba przekonam się do guidów :) Robię teraz tak, że tworzę klasę statyczną w której przechowuje nazwy kolumn (internalname), ale teraz już przejdę na GUIDy.

    Z jedną rzeczą nie mogę się zgodzić w twoim poście. Kolumnę typy lookup można utworzyć z poziomu kodu XML :) Procedura jest prosta, przed tworzeniem kolumny musi istnieć lista do której lookup miałby się odnosić. Przy tworzenie kolumny lookup podajesz nazwę (internalname) listy źródłowej. To wszystko. Robię tak od jakiegoś czasu i działa :)

  2. no widzisz, czlowiek uczy sie caly czas a google zawsze prawdy nie powie :) ale pomysl wprowadzenia internal name listy jest niezly :) sprawdze to niebawem :) dzieki jeszcze raz

Comments are closed.