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 (aktualnie czytasz)
  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

W poprzedniej części opisałem sposób dostępu do obiektu SPListItem za pomocą braku mapowania – dostęp odbywał się bezpośrednio przez obiekt SPListItem. W drugiej części opiszę to, co zastosowaliśmy w jednym z niesławnych projektów w 2007 roku.

W wspomnianym projekcie, jak tylko zorientowaliśmy się, że SharePoint nie posiada silnie typowanych pól, stwierdziliśmy, że trzeba coś z tym zrobić. Jako że czas gonił a na temat SPS w tych czasach prawie w ogóle postów nie było wymyśliliśmy najszybszy sposób silnie typowanego dostępu do danych jaki mogliśmy wykonać w krótkim czasie – wrapper. Cały kod i przykłady użyte w postcie pochodzą z okresu 2007 roku. Chciałbym to po prostu zaznaczyć :)

Wrapper

Mówiąc krótko, wrapper opakowuje dostęp do elementów poprzez zunifikowany interfejs starając się ukryć poorly designed or complicated interface.

To co chcieliśmy osiągnąć to podmienić wywołanie SPListItem[„Name”] na ClassName.Name a przy tym udostępnić prosty, zunifikowany dostęp do danych. Nie interesował nas przy tym typ zwracanych danych – to znaczy, mogliśmy żyć z tym iż zostanie nam zwrócony obiekt SPFieldLookupValue czy SPFieldUserValue, zaś co do pozostałych to chcieliśmy by dane zwrócone były value type.

Czyli do prostej listy z tytułem (pole wymagane) oraz liczbą (pole niewymagane) chcieliśmy mieć następujący dostęp:

public partial interface IItemEntity
{				
    int Id { get; }
    Guid UniqueId { get; }
    DateTime Created { set; get; }
    DateTime Modified { set; get; }
    SPAttachmentCollection Attachments { get; }
    string Title { set; get; }
    SPFieldUserValue Author { set; get; }
    double? Number { set; get; }
    SPFieldUserValue Editor { set; get; }
}

Także chcieliśmy mieć możliwość tworzenia, aktualizacji i usuwania obiektów za pomocą wrappera:

public partial class ItemEntity : IItemEntity
{
    private SPListItem _spListItem;

    public ItemEntity(SPListItem listItem)
    {
	    if(listItem == null)
	    {
		    throw new ArgumentNullException("listItem");
	    }
    				
	    this._spListItem = listItem;
    }

    public ItemEntity()
    {
	    SPSite spSite = SPContext.Current.Site;
	    SPWeb spWeb = spSite.OpenWeb(ItemEntityConsts.Web);
	    SPList spList = spWeb.Lists[ItemEntityConsts.List];
	    this._spListItem = spList.Items.Add();
    }

    public ItemEntity(SPWeb web)
    {
	    if(web == null)
	    {
		    throw new ArgumentNullException("web");
	    }
    	
	    SPSite spSite = web.Site;
    	
	    SPWeb spWeb = spSite.OpenWeb(ItemEntityConsts.Web);
	    SPList spList = spWeb.Lists[ItemEntityConsts.List];
    	
	    this._spListItem = spList.Items.Add();
    }
    		
    public void Update()
    {
	    this._spListItem.Update();			
    }

    public void Delete()
    {
	    this._spListItem.Delete();
    }
   
    // ... usuniete properties
}

Jednakże sam wrapper nie dawał nam pełnych możliwości, musieliśmy dorobić jeszcze data access layer, który udostępniał nam metody operujące na obiekcie lub kolekcji obiektów – teraz taki layer można by nazwać repozytorium.

public partial interface IItemDataAccess
{
    ICollection<IItemEntity> Items { get; }

    ICollection<IItemEntity> GetItemsByQuery(string query);
    ICollection<IItemEntity> GetItemsByQuery(string query, SPWeb web);
    ICollection<IItemEntity> GetItemsByQuery(string query, int limit);
    ICollection<IItemEntity> GetItemsByQuery(string query, int limit, SPWeb web);
    IItemEntity GetItemById(int id);
    IItemEntity GetItemById(int id, SPWeb web);
    IItemEntity GetItemByUniqueId(Guid uniqueId);
    IItemEntity GetItemByUniqueId(Guid uniqueId, SPWeb web);
    IItemEntity CreateItemInstance();
    IItemEntity CreateItemInstance(SPWeb web);		
    IItemEntity CreateItemInstance(SPListItem listItem);
}

Podsumowując, całość interfejsów wyglądała tak:

// IDki pol
internal static class ItemEntityConsts
{
    internal static Guid Title = new Guid("fa564e0f-0c70-4ab9-b863-0177e6ddd247"); 
    internal static Guid Author = new Guid("1df5e554-ec7e-46a6-901d-d85a3881cb18"); 
    internal static Guid Number = new Guid("342bae15-79ae-4773-8039-7ff651579928"); 
    internal static Guid Editor = new Guid("d31655d1-1d5b-4511-95a1-7a09e9b75bf2"); 
    internal static string Created = "Created"; 
    internal static string Modified = "Modified"; 
    internal static string Web = "/"; 
    internal static string List = "Items";
}

// interfejs dla entity
public partial interface IItemEntity
{
    int Id { get; }    
    Guid UniqueId { get; }    
    DateTime Created { set; get; }    
    DateTime Modified { set; get; }    
    SPAttachmentCollection Attachments { get; }    
    string Title { set; get; }    
    SPFieldUserValue Author { set; get; }    
    double? Number { set; get; }    
    SPFieldUserValue Editor { set; get; }    
    void Update();    
    void Delete();
}
// interfejs dostepu do danych
public partial interface IItemDataAccess
{
    ICollection<IItemEntity> Items { get; }    
    ICollection<IItemEntity> GetItemsByQuery(string query);    
    ICollection<IItemEntity> GetItemsByQuery(string query, SPWeb web);    
    ICollection<IItemEntity> GetItemsByQuery(string query, int limit);    
    ICollection<IItemEntity> GetItemsByQuery(string query, int limit, SPWeb web);    
    IItemEntity GetItemById(int id);    
    IItemEntity GetItemById(int id, SPWeb web);    
    IItemEntity GetItemByUniqueId(Guid uniqueId);    
    IItemEntity GetItemByUniqueId(Guid uniqueId, SPWeb web);    
    IItemEntity CreateItemInstance();    
    IItemEntity CreateItemInstance(SPWeb web);		    
    IItemEntity CreateItemInstance(SPListItem listItem);
}

Implementacja ItemEntity tak:

public partial class ItemEntity : IItemEntity
{
    private SPListItem _spListItem;

    public ItemEntity(SPListItem listItem)
    {
        if (listItem == null)
        {
            throw new ArgumentNullException("listItem");
        }

        this._spListItem = listItem;
    }

    public ItemEntity()
    {
        SPSite spSite = SPContext.Current.Site;
        SPWeb spWeb = spSite.OpenWeb(ItemEntityConsts.Web);
        SPList spList = spWeb.Lists[ItemEntityConsts.List];
        this._spListItem = spList.Items.Add();
    }

    public ItemEntity(SPWeb web)
    {
        if (web == null)
        {
            throw new ArgumentNullException("web");
        }

        SPSite spSite = web.Site;

        SPWeb spWeb = spSite.OpenWeb(ItemEntityConsts.Web);
        SPList spList = spWeb.Lists[ItemEntityConsts.List];

        this._spListItem = spList.Items.Add();
    }

    public void Update()
    {
        this._spListItem.Update();
    }

    public void Delete()
    {
        this._spListItem.Delete();
    }
    				
    public int Id 
    { 
        get { return this._spListItem.ID; } 
    }

    public Guid UniqueId
    {
        get { return this._spListItem.UniqueId; }
    }

    public DateTime Created
    {
        set { this._spListItem[ItemEntityConsts.Created] = value; }
        get { return (DateTime)this._spListItem[ItemEntityConsts.Created]; }
    }

    public DateTime Modified
    {
        set { this._spListItem[ItemEntityConsts.Modified] = value; }
        get { return (DateTime)this._spListItem[ItemEntityConsts.Modified]; }
    }

    public SPAttachmentCollection Attachments
    {
        get { return this._spListItem.Attachments; }
    }

    public string Title	
    {
        set
        {
            if(string.IsNullOrEmpty(value))
            {
                throw new ArgumentNullException("value");
            }					

            this._spListItem[ItemEntityConsts.Title] = value;
        }
        get
        {
            try
            {
                return (string)this._spListItem[ItemEntityConsts.Title];
            }
            catch(InvalidCastException ex) 
            {
                string message = string.Format(CultureInfo.InvariantCulture, ExceptionMessages.EntityStrongTypingException, "ItemEntityConsts.Title", "Items");
                throw new System.Data.StrongTypingException(message, ex);
            }
        }
    }
    	
    public SPFieldUserValue Author	
    {
        set
        {
            this._spListItem[ItemEntityConsts.Author] = value;
        }
        get
        {
            try
            {
                if(this._spListItem[ItemEntityConsts.Author] != null)
                {
                    return new SPFieldUserValue(this._spListItem.Web, this._spListItem[ItemEntityConsts.Author].ToString());
                }
                else
                {
                    return null;
                }
            }
            catch(InvalidCastException ex) 
            {
                string message = string.Format(CultureInfo.InvariantCulture, ExceptionMessages.EntityStrongTypingException, "ItemEntityConsts.Author", "Items");
                throw new System.Data.StrongTypingException(message, ex);
            }
        }
    }

    public double? Number
    {
        set
        {
            this._spListItem[ItemEntityConsts.Number] = value;
        }
        get
        {
            try
            {
                return (double?)this._spListItem[ItemEntityConsts.Number];
            }
            catch(InvalidCastException ex) 
            {
                string message = string.Format(CultureInfo.InvariantCulture, ExceptionMessages.EntityStrongTypingException, "ItemEntityConsts.Number", "Items");
                throw new System.Data.StrongTypingException(message, ex);
            }
        }
    }

    public SPFieldUserValue Editor	
    {
        set
        {
            this._spListItem[ItemEntityConsts.Editor] = value;
        }
        get
        {
            try
            {
                if(this._spListItem[ItemEntityConsts.Editor] != null)
                {
                    return new SPFieldUserValue(this._spListItem.Web, this._spListItem[ItemEntityConsts.Editor].ToString());
                }
                else
                {
                    return null;
                }
            }
            catch(InvalidCastException ex) 
            {
                string message = string.Format(CultureInfo.InvariantCulture, ExceptionMessages.EntityStrongTypingException, "ItemEntityConsts.Editor", "Items");
                throw new System.Data.StrongTypingException(message, ex);
            }
        }
    }
}

 

Implementacja Data Access zaś tak:

public partial class ItemDataAccess : IItemDataAccess
{
    public ICollection<IItemEntity> Items
    {
	    get { return this.GetItemsByQuery("", -1); }
    }

    public ICollection<IItemEntity> GetItemsByQuery(string query)
    {
	    return this.GetItemsByQuery(query, -1);
    }

    public ICollection<IItemEntity> GetItemsByQuery(string query, SPWeb web)
    {
	    return this.GetItemsByQuery(query, -1, web);
    }

    public ICollection<IItemEntity> GetItemsByQuery(string query, int limit)
    {
        ICollection<IItemEntity> list = new Collection<IItemEntity>();

        SPSite spSite = SPContext.Current.Site;

        using(SPWeb spWeb = spSite.OpenWeb(ItemEntityConsts.Web))
        {
            SPList spList = spWeb.Lists[ItemEntityConsts.List];

            SPQuery spQuery = new SPQuery();

            spQuery.Query = query;

            if(limit >= 0)
            {
                spQuery.RowLimit = (uint)limit;			
            }

            SPListItemCollection listItems = spList.GetItems(spQuery);

            foreach(SPListItem listItem in listItems)
            {
                list.Add(new ItemEntity(listItem));
            }

            return list;
        }
    }

    public ICollection<IItemEntity> GetItemsByQuery(string query, int limit, SPWeb web)
    {
        if(web == null)
        {
            throw new ArgumentNullException("web");
        }

        ICollection<IItemEntity> list = new Collection<IItemEntity>();

        SPSite spSite = web.Site;

        using(SPWeb spWeb = spSite.OpenWeb(ItemEntityConsts.Web))
        {
            SPList spList = spWeb.Lists[ItemEntityConsts.List];

            SPQuery spQuery = new SPQuery();

            spQuery.Query = query;

            if(limit >= 0)
                spQuery.RowLimit = (uint) limit;			

            SPListItemCollection listItems = spList.GetItems(spQuery);

            foreach(SPListItem listItem in listItems)
            {
                list.Add(new ItemEntity(listItem));
            }

            return list;
        }
    }

    public IItemEntity GetItemById(int id)
    {
        SPSite spSite = SPContext.Current.Site;

        using(SPWeb spWeb = spSite.OpenWeb(ItemEntityConsts.Web))
        {
            SPList spList = spWeb.Lists[ItemEntityConsts.List];

            return new ItemEntity(spList.GetItemById(id));
        }
    }

    public IItemEntity GetItemById(int id, SPWeb web)
    {
        if(web == null)
        {
            throw new ArgumentNullException("web");
        }

        SPSite spSite = web.Site;

        using(SPWeb spWeb = spSite.OpenWeb(ItemEntityConsts.Web))
        {
            SPList spList = spWeb.Lists[ItemEntityConsts.List];

            return new ItemEntity(spList.GetItemById(id));
        }
    }

    public IItemEntity GetItemByUniqueId(Guid uniqueId)
    {
        SPSite spSite = SPContext.Current.Site;

        using(SPWeb spWeb = spSite.OpenWeb(ItemEntityConsts.Web))
        {
            SPList spList = spWeb.Lists[ItemEntityConsts.List];

            return new ItemEntity(spList.GetItemByUniqueId(uniqueId));
        }
    }

    public IItemEntity GetItemByUniqueId(Guid uniqueId, SPWeb web)
    {
        if(web == null)
        {
            throw new ArgumentNullException("web");
        }

        SPSite spSite = web.Site;

        using(SPWeb spWeb = spSite.OpenWeb(ItemEntityConsts.Web))
        {
            SPList spList = spWeb.Lists[ItemEntityConsts.List];

            return new ItemEntity(spList.GetItemByUniqueId(uniqueId));
        }
    }

    public IItemEntity CreateItemInstance()
    {
        return new ItemEntity();
    }

    public IItemEntity CreateItemInstance(SPWeb web)
    {
        return new ItemEntity(web);
    }

    public IItemEntity CreateItemInstance(SPListItem listItem)
    {
        return new ItemEntity(listItem);
    }
}

Wady

  • Rozwiązanie jest dość ciężkie, i działa na obiektach SharePoint cały czas;
  • Przy posiadaniu własnych pól musimy je zwracać zamiast domyślnych wartości – wymaga edycji;
  • Nie możemy obiektu przekazać gdzie indziej ze względu na powiązanie do modelu obiektowego SharePoint;
  • Trzeba pamiętać o wykonaniu Update;
  • Brak zarządzania stanem – możliwość dostępu do danych nie istniejących lub do danych już zmienionych.

Zalety

  • Szybki i sprawny, silnie typowany dostęp do modelu obiektowego SharePoint;
  • Silne typowanie;
  • Dzięki interfejsowi, możliwe kilka implementacji;
  • W porównaniu z brakiem mappowania, jedno miejsce dostępu (dzięki czemu wystarczy, że zmienimy kod w wrapperze);
  • Pomoc kompilatora, jeżeli typ danych zwracanych jest różny – brak tej opcji w braku mappowania np.: var a = item[“name”], czym tutaj jest a?

Rozwiązanie

Ze względu na rozmiar projektu, nikt z nas nie chciał klepać tego kodu – przy jednej czy dwóch listach nie ma problemu, ale przy 50… co łącznie daje minimum 50*5=250 plików może to być trochę uciążliwe. Na szczęście z pomocą przyszedł nam CodeSmith Tools, dzięki któremu w dwa dni napisaliśmy sobie klasy generujące kod a potem jedynie konfigurację do naszych istniejących list.

Szablon, który użyliśmy załączam do postu – może komuś się przyda. Problem polega na tym iż był on pisany w lutym 2007 roku, a ostatnia jego aktualizacja była w lipcu 2007 roku i szczerze mówiąc nie pamiętam czy aby na pewno wszystkie typy pól SharePointa były w nim dobrze obsłużone :( Powyższy przykład został wygenerowany właśnie za pomocą tego narzędzia, więc na pewno działa (przynajmniej częściowo). Na rzecz tego postu trochę go zmodyfikowałem – usunąłem jeden poziom dostępu do danych, który istniał u nas jednak nie jest wymagany do poprawnego dostępu do danych. Także wykasowałem nazwę projektu z plików (na wszelki wypadek).

Rozwiązanie składa się z następujących plików:

  • CommonScript.cs – udostępnia proste operacje na przestrzeni nazw oraz widoczności elementów;
  • ProjCommonScript.cs – tworzy tablicę nazw pól, wykorzystywaną w pozostałych plikach;
  • ProjIDataAccess.cst – szablon generujący interfejs dostępu do danych;
  • ProjDataAccess.cst – szablon generujący implementację interfejsu dostępu do danych;
  • ProjEntitiesConsts.cst – GUIDy dostępu do pól;
  • ProjIEntities.cst – szablon generujący interfejs encji;
  • ProjEntities.cst – szablon generujący implementację interfejsu encji;
  • Proj.ClassGenerator.cst – szablon uruchamiający pozostałe szablony z przekazanymi ustawieniami generowania;
  • Proj.CommonSettings.cst – definicja parametrów generowania szablonów;
  • Gen_List_Settings.csp – przykładowe wartości parametrów z możliwością wygenerowania kodu.

Przykładowe parametry (sposób konfiguracji):

<?xml version="1.0"?>
<codeSmith xmlns="http://www.codesmithtools.com/schema/csp.xsd">
  <propertySets>
    <propertySet template="Proj.ClassGenerator.cst">
      <property name="SharePointSite">http://URL TO SPS SITE</property>
      <property name="SharePointWeb">/[URL TO WEB IF ANY]</property>
      <property name="SharePointList">LIST NAME</property>
      <property name="OutputDirectory">_GenOutput FOLDER</property>
      <property name="ClassName">CLASS NAME</property>
      <property name="Accessibility">Public</property>
      <property name="IncludeNamespaces">True</property>
      <property name="ModuleName">DONT REMEMBER</property>
      <property name="ReturnAllItems">NAZWA WIELU ELEMENTOW NP Items</property>
      <property name="ReturnSingleItem">NAZWA JEDNEGO ELEMENTU NP Item</property>
    </propertySet>
  </propertySets>
</codeSmith>

Po skonfigurowaniu pliku CSP, klikamy na niego prawym przyciskiem myszy i wybieramy opcję Generate Outputs:

generateOutputs

UWAGA: Szablon udostępniony jest na zasadach „AS IS”, bez gwarancji, że będzie on działał. Jeżeli jednak zdecydujecie się na to podejście i będziecie mieć problem z szablonem, dajcie znać w miarę możliwości postaram się pomóc.

WrapperGenerator.zip (13.38 kb)

6 KOMENTARZE

  1. Zaciekawił mnie temat, ale spojrzałem tylko w jedno miejsce i już wiem, że to jest zło. Ta linia dyskfalifikuje to rozwiązanie: spList.Items.Add(); Wogóle odwoływanie się do property Items może mieć sens w 1 na 100 przypadkach.

  2. MarqSS,

    zgadzam sie, ze odwolywanie sie do Items nie nalezy do najlepszych rozwiazan i sa powody by tego nie robic sa tez powody by zrobic to raz i potem dzialac na otrzymanej kolekcji.

    jezeli zas chodzi o dodawanie elementow na liste to ja znam jedynie trzy metody:
    1) poprzez Web Service
    2) poprzez batch upload
    3) poprzez Items.Add();

    zreszta jak sie przyjrzysz konstruktorowi SPListItem to zobaczysz ze jeden z parametrow to jest SPListItemCollection.

    nie zaleznie od mapowania zawsze spotykam sie z Items.Add i sam to stosuje.

    wiec to co jedyne mozna by zrobic to:
    var col = list.Items;
    var item = col.Add();

    tak czy siak narazamy sie na pobranie calej kolekcji elementow dla danego View, stad tez proby optymalizacji dostepu do istniejacego elementu poprzez SPQuery czy inne metody dostepu do danych SharePointowych.

    Jednakze jezeli chodzi o tworzenie elementow to tak jak napisalem, nie znam innym metod niz te ktore wymienilem wyzej. Jezeli znasz taka metode to daj znac, z checia sie z nia zapoznam.

    Gutek

  3. Jeśli odwołanie do Items potrafi trwać bite 3 minuty to jaki ma to sens? Osobiście staram się unikać ręcznego tworzenia obiektów wiedząc jakie niesie to za sobą konsekwencje. Jeśli jest coś do zrobienia z danymi obiektu to często wystarczy ItemAdded (robota tam może być już dowolnie skomplikowana). Oczywiście, nie zawsze mamy taką wygodę. Wtedy posiłkuje się choćby takimi rozwiązaniami:

    public static SPListItem OptimizedAddItem(SPList list)
    {
    const string EmptyQuery = "0";
    SPQuery q = new SPQuery {Query = EmptyQuery};
    return list.GetItems(q).Add();
    }

    Naprawdę nie polecam też metod, które zaprezentowałeś GetById lub GetByUniqueId. Jest to wielkie zło, przez które parę osób nabawiło się nerwicy.
    Sensowna opcja to
    a) prosta = SPQuery (i to może zostać), ale !!! SPQuery z Recurvise jest też złem i potrafi trwać kilka minut.
    b) pozostaje trudniejsze podejście -> PortalSiteMapProvider

    Wyznaje prostą filozofię, jeśli coś może przynieść przykre konsekwencje (a przyniesie), to nie należy nikomu wystawić tego jako metody publicznej, bo na pewno ktoś, kiedyś tego użyje.

  4. Pomysl z dodaniem fajny podoba mi sie – ale lepiej by to wygldalo jako Extension method, dodaj this i bedzie ql.
    Pomysl z zabawa z ItemAdded przy uzupełnianiu pól (bo tak to zrozumialem) jest slaby gdyz niesie za soba pewne konsekwencje – po pierwsze zagniezdzenie Eventow – jezeli masz jakies inne eventy mozesz trafic na problem z aktualizacja juz zaktualizowanego obiektu. przypadkow moge podac wiele. ItemAdded wykorzystuje wtedy i tylko wtedy kiedy na podstawie danych dodanego elementu mam wykonac jakas operacje a nie aktualizowac jego dane – chyba ze Cie zle zrozumialem.

    Co do "nie polecania" metod GetById i GetByUniqueId to nie wiem czego w nich nie polecasz. Mowisz o sensownej opcji SPQuery a ich implementacja wlasnie go wykorzystuje. Chyba ze masz do nich pretensje o wykorzystanie RecursiveAll ale w takim momencie zastanawia mnie skad wiesz jakie ID jest w jakim katalogu. Bo jezeli wiesz w jakim katalogu masz przeszukac dane to nie ma problemu. Opcja RecursiveAll nie wplywa na wydajnosc kiedy nie ma i kiedy sa katalogi – zawsze bedzie taka sama (rosla wraz z liczba elementow na liscie). Bez RecursiveAll bedzie szybciej ale kiedy wiesz gdzie czego szukac. bo jak masz zagniedzenie 10 katalogow stworzonych przez uzytkownika i masz pobrac dla niego element o ID=20 to bez wiedzy w ktorym on jest takalogu wykorzystsz SPQuery z RecursiveAll lub w MOSS PortalSiteMapProvider czy Search. Jezeli zas chodzi o wyniki pobierania kolumny po ID a po indexed column, przy listach do 30/40K nie widzialem na tyle wyrazniej roznicy – jezeli w ogole jakas byla (a pewnie minimalna byla) zeby warto bylo stosowac inne zapytanie – ale to tez może być uzależnione od serwerow (fizycznie).

    Jezeli chcesz tak naprawde tworzyc tylko i wylacznie rozwiazania, ktore beda bardzo dobrze dzialaly dla X tysiecy elementow to spoko, ale przy malej liczbie elementow na listach PortalSiteMapProvider czy Search nie sa najlepszymi rozwiazaniami a przy tworzeniu aplikacji dla WSS mozesz tak naprawde o nich zapomniec. Przy rozwiazaniach gdzie na liscie masz max 1k elementow nawet nie oplaca sie przemeczac bo roznicy w czasie dostępu nie zaobserwujesz.

    W zyciu raz mi sie zdazylo by rzeczywiście załadowanie Items trwalo dluzej niz 1 minute bylo to wtedy kiedy na liscie mialem powyzej 20K elementow. Od tamtej pory projektuje tak rozwiazania by taka ilosc danych nie byla przechowywana na liscie lub jezeli juz ma byc to jest tworzony rozsadny system zarzadzania poprzez foldery jako kontenery dla kolejnych 2K elementow. Przy rozwiazaniach 100K w zwyz elementow na liscie korzystam z external DB.

    Jezeli jednak mialbym sie zabrac za tworzenie roziwazania, ktore ma przechowywac po 100K elementow na liscie, to zrobilbym kilka rzeczy:
    1) zastanowilbym sie nad mozliwoscia posorotowania po kontenerach (folderach) – ja na to wplywam, czy uzytkownik
    2) zastanowilbym sie czy moze lepiej nie rozbic tego na kilka list, skoro moze istniec kontener to moze tez istniec lista?
    3) zastanawilbym sie czy uzytkownik musi miec dostepny standardowy widok listy czy moge ja go stworzyc za pomoca SPGridView?
    4) po podieciu wyzszych decyzji i jeszcze pewnie kilku pomniejszych dopiero bym sie zastanowil jaki teraz bedzie najlepszy dla mnie system odpytywania listy. nie korzystal bym z zalecen – raczej sprawdzilbym czy maja one sens w danym rozwiazaniu.

    Doslownie problem ze skalowalnoscia list w SharePoint jest mozna powiedziec taki sam kiedy masz przeskalowac baze danych by mogla obiac 500mln zarejestrowanych uzytkownikow wraz z ich ustawieniami. Wtedy zastanawiasz sie czy tworzyc baze danych na podstawie liter i serwery dla liter nazwiska, a co jezeli nazwisk na to bedzie wiecej niz 20mln czy rozbic to na kolejne serwery? itp. itd. Jednakze zaczynasz od jednej listy i prostego zapytania gdyz jest ono wystarczajaca dla potrzeb.

    Zreszta w nowym SharePoint zostal wprowadzony throttling ktory ma na celu uzmyslownie uzytkownika ze lista/widok ma juz troche za duzo danych. To samo co ja robie uzmyslawiajac klientow. Bo niezaleznie jak ty bedziesz sie staral w kodzie all ograniczyc i/lub napisac wydajnie zgodnie z zasadami o ktorych mowisz (http://go.microsoft.com/fwlink/?LinkId=95450&clcid=0x409) uzytkownik koncowy i tak o tym moze nie wiedziec i potem sobie nabrudzi.

    Podsumowujac, rozumiem dlaczego robisz takie zapytania, ale nie zgadzam sie ze to są one jedynymi słusznymi :)

  5. Gdy czytam, że pojawił się Wrapper dla SPListItem to spodziewam się, że jest uniwersalny. No, może gdyby to był z założenia wrapper dla dla małych list (< 10k elementów na liście), to bym uznał, że jest ok. Zatem jak podsumowałeś swój komentarz: jeśli szukasz rozwiązania generycznego, np wrappera to powinien być on jedyny słuszny ;), jeśli zaś bawisz się z malutkimi listami, to masz wiele podejść.
    Napisałeś też, że projektujesz tak aby na liście nie było dużo elementów. Tylko jak bardzo komplikuje się złożoność logiki? Pierwszy lepszy przykład: firma spedycyjna – system przechowuje dane przesyłek poleconych na liście. Na drugiej liście masz reklamacje, gdzie znajduje się lookup do przesyłki. Oczywiście lista przesyłek w rok urośnie Ci do 100tyś i więcej. Oczywiście możesz rozbić to na wiele list, ale nagle lookup przestaje być taki prosty w realizacji, a klient się pyta a czemu raz mam kliknąć tu, a raz tu. Pozostaje skomplikować logikę, albo użyć wydajniejszego dostępu do danych. Abstrahuje już od zgłębiania dylematu: czy nie lepiej użyć coś innego niż SharePoint dla rozwiązania problemu firmy spedycyjnej? [wg niektórych ludzi z MS – SP nadaje się do wszystkich systemów na świecie i naprawdę warto wydać każda kasę na tą platformę].

    Co do ItemAdded to nie zawsze chodzi nam o modyfikację danych. Właściwie jeśli już to zawsze pozostaje wajcha (z pamięci, nie pamiętam sygnatury metody) DisableEventFiring() i Enable…. Niemniej czasem trzeba zrobić coś na innej liście, czasem w zewnętrzym systemie. Scenariuszy jest naprawdę wiele.

    Co do UniqueId, to miałem na myśli fakt, że domyślnie brane są wszystkie atrybuty obiektu i na takich pracuje SP, co w przypadku ciężkich elementów list znacznie spowalnia wykonanie zapytania. Znów, w pewnych przypadkach nie będzie miało to znaczenia, a w pewnych będzie miało kolosalne.

  6. Nie pisze ze pojawil sie Wrapper, oj nie. Zauwaz ze nie pisze "proponuje wam takie rozwiazanie", tylko "opisze rozwiazanie, ktore zastosowalismy", moim zdaniem tutaj jest mala roznica w znaczeniu. Opisuje podejscie – wiec tutaj moglismy sie nie zrozumiec. Dodatkowo daje szablon z lutego 2007 roku kiedy to jak ktos wiedzial ze nalezy pozbyc sie obiektu SPSite czy SPWeb to juz byl sukces. Jezeli masz szablon i uwazasz ze cos jest w nim nie tak dla swoich potrzeb, to nie ma problemu bys go zaktualizowal – zachecam do tego jezeli chcesz skorzystac z wrappera to smialo, kilka modyfikacji w CodeSmith i bedziesz mial takie zapytania jakie chcesz miec. seria o mapowaniu obiektow sie jeszcze nie zakonczyla a to dopiero drugi post, ale na pewno nie bede w nim poruszal wydajnosci zapytan na liste SharePointowa, czy wydajnosci w tworzeniu elementow. Do kazdego podejscia kazdy bedzie mogl zastosowac swoje praktyki i swoje rozwiazania. Dodatkowo w mapowaniu jak juz mapuje liste na obiekt to zawsze mnie interesuja wszystkie pola elementu a nie tylko jedno – jak chce jedno lub dwa to robie zapytanie zwracajace mi Tuple lub cokolwiek innego co da mi te dane ktore chce, a nie obiekt.

    Co do problemu z firma spedycyjna, to tak jak mowie albo bym to zrobil na extern DB z dostepem poprzez SharePointa – nie ma z tym w ogole problemu, ludzie beda widziec dane, bedzie mozna je przeszukiwac. Jedynie trzeba bedzie wiecej czasu poswiecic na napisanie widokow lub wykorzystac BDC jak sie ma MOSS. Dodatkowo po roku napewno dane bym archiwizowal itp. itd. Zas co do "cliknac tu i tu" to znaczy ze cos zostalo zle zaprojektowe, nalezaloby tak ukryc te dane przed uzytkownikiem i taki interfejs mu zaproponowac zeby nie odczul roznicy. Poprzez rozsada architekture rozwiazania takze klopotow za wiele nie bedzie przy robieniu zapytan, ale nie wazne.

    Co do tego ze SP jest do wszystkiego, to tak jak IIS nadaje sie do hostowania JSP. Pewnie sie da jakos JSP z hostowac na IIS, ale po co? wiec jedno to co jest mowione a drugie do czego mozna SharePointa zastosowac – choc z wersja 2010 pewnie do wiekszej liczby rzeczy niz 3.0. na ten temat nie ma sensu dyskutowac – jak ktos lubi SPS to do wszystkiego zastosuje, jak ktos nie lubi do do niczego a jak ktos lubi i nie lubi to bedzie wybieral :)

    DisableEvent… nie zawsze dziala dla przkladu: http://blog.gutek.pl/post/2009/01/15/Tips-Tricks-10-Wykorzystanie-ukrytego-SuppressAfterEvents-na-SPListItem.aspx.

    co do pol pobieranych to spojrz na impelemntacje w 2010, sa nowe metody ktore przyjmuja ViewFields jako parametr przy GetItemById na przyklad.

    Gutek

Comments are closed.