wtorek, 6 kwietnia 2010

Stronicowanie w DataGrid

Korzystanie z kontrolki DataGrid jest niezwykle proste. Korzystając z źródła danych (zapytanie SQL czy używając jakiego systemu ORM np. LINQ) bindujemy go z naszą kontrolką:

    1                 SqlConnection conn = new SqlConnection(@"Data Source=ACER5738\sqlexpress;Initial Catalog=AdventureWorks;Integrated Security=True");
    2                 SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Person.Contact", conn);
    3                 DataSet ds = new DataSet();
    4                 da.Fill(ds, "Contact");
    5                 grid.DataSource = ds;
    6                 grid.DataBind();

Takie zbindowanie spowoduje wypełnienie DataGrida wszystkimi danymi z tabeli Contact. Wypełnienie to jest jak najbardziej poprawne o ile w tabeli nie ma olbrzymich ilości danych. W przypadku gdy mamy wyświetlić tysiące wierszy, użytkownik naszej aplikacji może odczuć narzut spowodowany przesłaniem ogromnych ilości danych.

Aby zachować dużą wydajność naszej aplikacji można zastosować stronicowanie. Dzięki niemu dane zostają podzielone na kilka stron tzn. na pierwszej stronie mamy wyniki od 1 do 20 wiersza naszej tabeli na drugiej 21 - 40 itd.

Istnieją dwie możliwości implementacji tego rozwiązania. Pierwsza oferowana przez kontrolkę oraz druga, którą należy samemu zaimplementować.

Zobaczmy najpierw na prostszą wersję tego rozwiązania czyli możliwości oferowane stricte przez kontrolkę. Aby włączyć Paging należy ustawić właściwośćAllowPaging na true. Dzięki właściwości PageSize ustalamy ile wierszy będzie widoczne na jednej stronie. Właściwość CurrentPageIndex określa numer wyświetlanej strony (licząc od 0). PageCount określa ilość wszystkich stron. Jeśli uruchomimy teraz nasz projekt to zobaczymy następujący ekran:
Kliknięcie na strzałkę odpowiedzialną za przewijanie stron nie zmienni niczego w wyświetlanej zawartości. Dzieje się tak dlatego, gdyż nie obsłużyliśmy zdarzenia PageIndexChanged:

    1         protected void grid_PageIndexChanged(object source, DataGridPageChangedEventArgs e)
    2         {
    3             SqlConnection conn = new SqlConnection(@"Data Source=ACER5738\sqlexpress;Initial Catalog=AdventureWorks;Integrated Security=True");
    4             SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Person.Contact", conn);
    5             DataSet ds = new DataSet();
    6             da.Fill(ds, "Contact");
    7             grid.CurrentPageIndex = e.NewPageIndex;
    8             grid.DataSource = ds;
    9             grid.DataBind();
   10         }

Teraz po kliknięciu na strzałkę zawartość będzie się płynnie zmieniała.
Po przetestowaniu tego rozwiązania dla kilku tysięcy wierszy okazuje się, że aplikacja przyspieszyła znacząco. Jednak co jeśli do tabeli będą trafiały dziesiątki danych a jej rozmiar nie będzie liczony w dziesiątkach tysięcy a setkach?
Otóż rozwiązanie oferowane przez kontrolkę ma podstawowy błąd, który może nie być zauważony przez programistę na samym początku pracy z nią. Podczas każdego przeładowania strony DataSet jest wypełniany całą zawartością tabeli Contact. Najlepszym rozwiązaniem byłoby zaimplementowanie metody dzięki której pobierane byłyby tylko te rekordy które są aktualnie wymagane.

Własna implementacja stronicowania.
Najpierw zmieniamy właściwość AllowCustomPaging na true. Następnie musimy ustawić właściwość VirtualItemCoun, która informuje o ilości wszystkich wierszy do wyświetlenia. Można to uczynić za pomocą prostej funkcji SQLa:

    1                 SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM  Person.Contact", conn);
    2                 conn.Open();
    3                 int count = (int)cmd.ExecuteScalar();
    4                 grid.VirtualItemCount = count;
    5                 conn.Close();

Teraz należy obmyślić w jaki sposób dostać się do odpowiednich wierszy. Najprostsze rozwiązanie otrzymamy gdy w naszej tabeli jest pole Id Autoincrement. Wtedy możemy skorzystać z naszych właściwości i pobierać dane w taki sposób:

            SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Person.Contact WHERE ContactID > " + (grid.PageSize * (e.NewPageIndex - 1)).ToString() + " AND ContactID <= " + (grid.PageSize * e.NewPageIndex).ToString(), conn);

Co jeśli w tabeli nie ma kolumny Autoincrement? Należy wtedy utworzyć tabelę tymczasową z dwoma kolumnami: pierwsza będzie autoincrement a druga np IDContact. Wypełniamy tabelę autoincrement danymi z tabeli Contact a następnie łączymy w zapytaniu obie tabele.

Zapytanie może wydać się skomplikowane, lecz jest skuteczne. Inną możliwością (dużo prostszą w implementacji) jest skorzystanie z zapytania LINQu:

    1             var db = new DataClasses1DataContext();
    2             var q = (from a in db.Contacts
    3                      select a).Skip(grid.PageSize * (e.NewPageIndex - 1)).Take(grid.PageSize);
    4             grid.CurrentPageIndex = e.NewPageIndex;
    5             grid.DataSource = q;
    6             grid.DataBind();

Instrukcja Skip pomija żądaną ilość wierszy w wyniku a instrukcja Take pobiera wskazaną ilość wierszy. Jeśli podglądnęlibyśmy zapytanie SQL które generuje LINQ:

{SELECT [t1].[ContactID], [t1].[NameStyle], [t1].[Title], [t1].[FirstName], [t1].[MiddleName], [t1].[LastName], [t1].[Suffix], [t1].[EmailAddress], [t1].[EmailPromotion], [t1].[Phone], [t1].[PasswordHash], [t1].[PasswordSalt], [t1].[AdditionalContactInfo], [t1].[rowguid], [t1].[ModifiedDate]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t0].[ContactID], [t0].[NameStyle], [t0].[Title], [t0].[FirstName], [t0].[MiddleName], [t0].[LastName], [t0].[Suffix], [t0].[EmailAddress], [t0].[EmailPromotion], [t0].[Phone], [t0].[PasswordHash], [t0].[PasswordSalt], [t0].[rowguid], [t0].[ModifiedDate]) AS [ROW_NUMBER], [t0].[ContactID], [t0].[NameStyle], [t0].[Title], [t0].[FirstName], [t0].[MiddleName], [t0].[LastName], [t0].[Suffix], [t0].[EmailAddress], [t0].[EmailPromotion], [t0].[Phone], [t0].[PasswordHash], [t0].[PasswordSalt], [t0].[AdditionalContactInfo], [t0].[rowguid], [t0].[ModifiedDate]
FROM [Person].[Contact] AS [t0]
) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER]
}

wydaje się bardzo skomplikowane, jednak nie odbiega od tego które sami poprzednio skonstruowaliśmy.

Podsumowując CustomPaging przydaje się wtedy gdy nasze źródło danych zawiera dużo wyników oraz zależy nam aby użytkownik nie musiał czekać na załadowanie danych przez dłuższy czas.

Brak komentarzy:

Prześlij komentarz