Wydajna paginacja przy użyciu XmlReadera

Właśnie przeczytałem sobie artykuł o paginacji (czyli dzieleniu listy wyników na strony) na wortalu http://xmlguru.net (artykuł opisuje w jaki sposób wykorzystać do paginacji arkusz Xslt), który skłonił mnie do pewnych przemyśleń. Wyszło mi coś takiego... Przetwarzanie dokumentów Xmlowych za pomocą Xslt jak najbardziej. Natomiast sama paginacja już chyba nie - bo można to zrobić o wiele wydajniej bez Xslt. Jak ? Zacznijmy po kolei.

Paginacja to w zasadzie dwie rzeczy
- wyświetlenie strony z wynikami dla wybranego numeru strony
- wyświetlenie linków umożliwiających nawigację pomiędzy stronami

Kilka spostrzeżen:
- zazwyczaj pokazujemy x rekordów gdzie x jest znacząco mniejsze niż liczba wszystkich rekordów (np. 20 z 1000)
- zazwyczaj ludzie zainteresowani są pierwszymi kilkoma stronami które często stanowią mniej niż 10% wyników

Stąd można wysnuć następujący wniosek - transformacja całego dokumentu Xmlowego (wszystkie wyniki) tylko w celu pokazania malutkiej jego cząstki (bieżąca strona) to trochę marnotrastwo prądu ponieważ parsowanie i ładowanie dokumentu do tzw. Xml cache'u (czyli obiektu typu XmlDocument, XPathDocument czy XDocument) jest kosztowne zarówno jeśli chodzi o czas (parsowanie tekstu, czas potrzebny na alokację pamięci) jak i pamięć (średnio można przyjąć że obiekt Xml w pamięci potrzebuje pieć razy więcej pamięci niż jego rozmiar na dysku). O wiele lepiej by było wybrać w jak najmniej kosztowny sposób tylko te rekordy które chcemy pokazać i użyć je jako źródło dalszej transformacji. Na platformie .NET do efektywnego czytania dokumentów Xmlowych służy XmlReader.  Od strony technicznej - na platformie .NET - całe rozwiązanie mogłoby to wyglądać tak:

1) Wyświetlanie wyników:
Za pomocą XmlReadera przeskakujemy węzły ze stron poprzedzających stronę którą mamy wyświetlić. Po dojściu do strony którą mamy wyświetlić czytamy x rekordów, które ładujemy do Xml cache'a. To co załadujemy przetwarzamy za pomocą arkusza Xslt. (Zauważmy - zawsze to będzie jedna strona wyników). Tutaj wyświetlanie wyników łączy się z nawigacją
Jeśli nie musimy pokazywać PRECYZYJNIE nawigacji (dokładnie chodzi o rzeczywistą liczbę stron) kończymy czytanie. W ten sposób Xml cache będzie zawierał tylko to co ma być pokazywane (oszczędność przy parsowaniu dokumentu, oszczędność pamięci, bardziej efektywne przetwarzanie po stronie Xslt). Jeśli potrzebujemy rzeczywistą liczbę stron to czytamy pozostałe węzły po osi following-sibling (ale nie ładujemy do xml cache'a) tylko po to, żeby policzyć całkowitą liczbę rekordów.

2) Nawigacja:
Najczęściej ludzi nie interesuje pokazanie setek linków do poszczególnych stron. Z reguły +/- 2 strony wystarczają. Taką nawigację można wygenerować częściowo "na pałę" - zakładając optymistycznie, że są jakieś strony po aktualnie wyświetlanej. Jeśli użytkownik kliknie na stronę której tak naprawdę nie ma to pokazywana jest ostatnia strona z odpowiednio poprawioną nawigacją (przypomina zachowanie popularnych search engine'ów? - powinno...). Drobnym usprawnieniem jest sprawdzanie czy bieżąca strona nie jest ostatnią, i - jeśli akurat tak jest - nie wyświetlanie linków do kolejnych stron.
Jeśli trzeba pokazać wszystkie linki można próbować oszacować liczbę stron (dzielimy wielkość pliku przez wielkość rekordów do pokazania) - cały czas nie jest to dokładne ale powinno wystarczyć. Ostatnia możliwość to pokazanie wszystkiego "zgodnie z prawdą" - w tym przypadku musimy policzyć wszystkie rekordy czyli przejść przez caly dokument źródłowy.

Po dawce teorii, czas na praktykę. Listing 2 zawiera kod zwracający zadaną strone z dokumentu Xmlowego. Kod działa dla dokumentów, w których rekordy dzielone na strony znajdują się bezpośrednio pod elementem root (są jego bezpośrednimi dziećmi). Przykład takiego wejściowego dokumentu Xmlowego został przedstawiony na listingu 1

(Źródła są też dostępne do ściągnięcia - link poniżej lub u góry strony).