SharePointem zajmuję się już od jakiegoś czasu i z każdą chwilą odkrywam jak bardzo jest to beznadziejna platforma. Kiedyś podsumuję przemyślenia, jakie zdarzyło mi się ostatnio mieć na ten temat. Jednak dziś coś co komuś kiedyś może się przydać (i co oszczędzi dużo zachodu w determinacji problemu).

Dzisiejszy problem dotyczy LookupFields w najnowszym SharePoincie 2010, a raczej problemów z nim związanych. Czym jest SPFieldLookup? Jest typem danych listy, który wskazuje na element w innej liście. Dla osób zaznajomionym z SQL to całkiem prosta, logiczna i przydatna rzecz. Jednak nie w Sharepoincie. Tutaj ta funkcjonalność jest zrobiona absolutnie bezsensownie i nieużytecznie, a co najgorsze – jest niezbędna do otrzymania pewnych widoków listy. Można jednak “wyklikać” te pola i będą one działać całkiem nieźle, ale jeśli trzeba napisać kod, który będzie to wykonywał…. tu sprawa wygląda zgoła inaczej. Ok, to nie owijamy w bawełnę tylko zaczynamy mierzyć się z problemem.

Opis problemu

Mamy 2 listy – źródłową (zawiera kolumny, do których chcemy zlinkować) i docelową (ma zawierać kolumny wskazujące na odpowiednie kolumny z listy źródłowej). Lista źródłowa zawiera 2 nas interesujące kolumny – ID oraz tytuł. I chcemy obie kolumny widzieć w liście docelowej.

Proste, nieprawdaż? Aż chciałoby się napisać jedną linijkę sql, która rozwiązuje problem…. ech…

Dodanie jednego Lookup’a

Dodanie jednego lookup’a jest względnie trywialne – o tym można było jeszcze znaleźć informacje w necie. Wygląda to mniej więcej tak:

LIST.Fields.AddLookup(fieldInternalName, web.Lists[lookupListName].ID, false); 
LIST.Update(); 
SPField field = LIST.Fields[fieldInternalName]; 
field.Title = fieldDisplayName; 
field.Update();

Działamy na jednej liście i jest względnie ok, należy jedynie zwrócić uwagę na ostatni argument funkcji AddLookup – required. Jeśli chcemy uwzględnić więcej pól niż jedno w naszej docelowej tabeli to musi być ustawione na false.

Dodawanie kolejnego Lookup’a

Cóż… tutaj zaczynają się schodki (z wbudowanymi zapadniami na każdym stopniu :)) Nie można bowiem dodać lookupa zależnego od innego, bo to najprawdopodobniej niezaimplementowana funkcja (!), co jest dużym utrudnieniem, bo była to jedyna rozsądna droga na wykonanie tego. Na szczęście można ten problem obejść generując field z kodu xml, który go opisuje. Jest to o tyle ciekawe, że dzięki temu możemy ustawić wiele pól, które standardowo są read-only (co już samo w sobie jest czasem idiotyzmem). Krótkie badanie w SharePoint Manager wskazuje na taki xml niezbędny do wygenerowania fielda, który nas usatysfakcjonuje:

<Field Type="Lookup" DisplayName="BudgetTypeId" List="{3bcd0a76-9bf4-4126-8686-db126e1654f1}" WebId="fec81976-8eb0-4ad2-84a6-ccdb699c892d" ShowField="ID" FieldRef="c8343421-6a06-4f0a-a174-7280b1aba001" ReadOnly="TRUE" UnlimitedLengthInDocumentLibrary="FALSE" ID="{a919bab5-f2d1-46a1-8426-950d4ba1b92f}" SourceID="{a572af76-2ce2-4bcc-bf02-962103baa23d}" StaticName="BudgetType_x003a_ID" Name="BudgetType_x003a_ID" Required="FALSE" Group="" Version="1" />

Jeśli jeszcze nie widać, że to droga masakrycznie naokoło, to podpowiem, że owszem. A najlepsze to to, że nie ma innej opcji, która działa xD

Tak mniej więcej wygląda ostateczna funkcja, która tworzy nasze 2 fieldy typu Lookup:

/// <summary>
/// Function creates 2 lookup fields - main lookup (title) and additional id field 
/// </summary> 
/// <param name="web"></param> 
/// <param name="lookupFieldName">Target field's internal name</param> 
/// <param name="lookupDisplayFieldName">Target field's title</param> 
/// <param name="listName">Target list's name</param> 
/// <param name="originListName">Source list's name</param> 
/// <param name="sourceFieldName">Source field's name or title</param> 
/// <param name="sourceFieldIdName">Source field's id</param> 
/// <returns>SPFieldLookup of main lookup with title</returns> 
public static SPFieldLookup CreateLookupFields(SPWeb web, string lookupFieldName, string lookupDisplayFieldName, string listName, string originListName, string sourceFieldName, string sourceFieldIdName)
 { 
	SPList originList = web.Lists[originListName];
	SPList list = web.Lists[listName]; 
	SPField catLookupId = CreateFieldLookup(web, lookupFieldName, lookupDisplayFieldName, listName, originListName, false); //to co było wcześniej
 	SPFieldLookup lookupField = catLookupId as SPFieldLookup; 
	lookupField.LookupField = originList.Fields[sourceFieldName].InternalName; 
	lookupField.ReadOnlyField = false; 
	lookupField.Required = false; 
	lookupField.AllowMultipleValues = false; 
	lookupField.Update();
	string xml = string.Format("<Field Type=\"Lookup\" DisplayName=\"{4}\" List=\"{0}\" WebId=\"{1}\" ShowField=\"ID\" FieldRef=\"{2}\" ReadOnly=\"TRUE\" UnlimitedLengthInDocumentLibrary=\"FALSE\" SourceID=\"{3}\" StaticName=\"{4}\" Name=\"{4}\" Required=\"FALSE\" Group=\"\" Version=\"1\"/>", list.ID, web.ID, lookupField.Id, originList.Fields[sourceFieldIdName].Id, sourceFieldIdName);
	list.Fields.AddFieldAsXml(xml); 
	list.Update(); 
	return lookupField; 
}

Zapis danych w liście z lookup-ami

Pozornie to nie koniec zabawy z tym cholerstwem. Okazuje się bowiem, że zapis (jak i odczyt za chwilę) także jest nietrywialny. Oczywistym jest przecież, że konstrukcja poniższego typu jest błędna:

SPListItem item; 
… 
item[“LookupField”] = “value”;

Tak zadeklarowane przypisanie zawsze będzie generować błąd taki, że dane pole jest typu Read Only. Wtf? Na szczęście błąd ten nie ma nic z tym wspólnego. Problem polega na tym, że dane, jakie mogą zostać wprowadzone w ten sposób muszą wyglądać tak: "<ID>;#<VALUE>". Ponadto wartość musi się zgadzać z ID w źródłowej liście by coś takiego przeszło. Nie pytajcie mnie czemu xD. Pola Id nie trzeba ustawiać, ustawi się automatycznie.

Odczyt danych z listy

Odczyt także jest nietrywialny, ale tym razem względnie umiarkowanie. Co dostaniemy chcąc wypisać pole naszego lookupowanego itemu:

Console.WriteLine(item[“LookupField”].ToString())

Ano coś takiego: "1;#Value". A co otrzymamy chcąc pokazać pole z Id? Ano taką wartość: "1;#1".

Jakież to było proste, czyż nie? Po co komu dokumentacja, skoro takie rozwiązania są tak niesamowicie oczywiste? Ech… Nazwijmy to podsumowaniem, bo nie widzę sensu pisać takowego… dla dobra SharePointa, paradoksalnie.