ADO.NET w praktyce


Proszę prześledzić poniższy przykład, który wywołuje asynchronicznie procedurę LongStoredProc:

  1. using (SqlConnection cnn = new SqlConnection(

    @"Data Source=NIEZAPOMINAJKA2\SQL2005;

    Initial Catalog=PCWKVS2005;

    Integrated Security=True;

    async=true;")) {

  2. SqlCommand cmd = cnn.CreateCommand();

  3. cmd.CommandText = "LongStoredProc";

  4. cmd.CommandType = CommandType.StoredProcedure;

  5. cnn.Open();

  6. IAsyncResult ar=cmd.BeginExecuteReader();

  7. SqlDataReader dr=cmd.EndExecuteReader(ar);

  8. dr.Close();

  9. cmd.BeginExecuteReader(new AsyncCallback(CallbackFun), cmd);

  10. for (int i = 0; i < 10000; i++) {

  11. Thread.Sleep(5000);

  12. }

  13. }

Najpierw definiujemy połączenie i SqlCommand (typu StoredProcedure). Następnie w linii 6 uruchamiamy proces asynchroniczny. W tym momencie SQL Server zaczyna wykonywać LongStoredProc. W linii 7 wykonujemy operację EndExecuteReader. Powoduje ona, że bieżący wątek czeka, aż procedura asynchroniczna zakończy działanie i zwróci odpowiedni wynik (w tym przypadku DataReader).

Operacje asynchroniczne ADO.NET 2.0 wykorzystują zaawansowane możliwości systemów Windows 2000/XP/2003.

Operacje asynchroniczne ADO.NET 2.0 wykorzystują zaawansowane możliwości systemów Windows 2000/XP/2003.

Dostępne są prawie wszystkie odmiany wywołań SqlCommand - nie można tylko wywołać ExecuteScalar:

BeginExecuteNonQuery / EndExecuteNonQuery

BeginExecuteReader / EndExecuteReader

BeginExecuteXmlReader / EndExecuteXmlReader

Proszę zauważyć, że między linią 6 a 7 może być dowolnie wiele operacji, można np. równolegle uruchomić wszystkie procedury, które zwracają informacje potrzebne do dalszego działania, a potem, w odpowiednich instrukcjach End poczekać, aż wynik zostanie wygenerowany (można też skorzystać z tych możliwości, jakie daje IAsyncResult - czyli np. pobranie uchwytu, sprawdzenie czy operacja zakończyła się itp.).

W linii 9 wywołujemy funkcję w inny sposób, używając tzw. metody zwrotnej (callback), która zostanie wywołana, gdy zakończona będzie operacja po stronie serwera SQL. Drugi parametr określa dodatkowy obiekt, który też będzie przekazany do CallbackFun.

Procedura zwrotna (callback) ma taką samą definicję jak w wypadku delegatów. Parametr przekazany przy wywołaniu (linia 9 poprzedniego wydruku) umieszczany jest w polu AsyncState. Po zrzutowaniu można uzyskać dostęp do danego SqlCommand i np. pobrać DataReader oraz wykonać na nim jakieś operacje:

private void CallbackFun(IAsyncResult ar) {

SqlCommand cm = (SqlCommand)ar.AsyncState;

SqlDataReader dr=cm.EndExecuteReader(ar)

while (dr.Read()) { ... }

dr.Close();

}

W przypadku funkcji zwrotnej trzeba samemu sprawić, by aplikacja poczekała na wynik - nie ma funkcji blokujących jak w poprzednim wypadku, stąd kod w liniach 10-12.

Warto też pamiętać, że w aplikacji WinForms wygodniejszym mechanizmem może okazać się BackgroundWorker - specjalny komponent, w którym można coś uruchomić w tle i poczekać, aż to coś zakończy działanie. Wynika to stąd, że w .NET nie można aktualizować interfejsu użytkownika z innego wątku niż tego, w którym została uruchomiona dana forma (w każdym razie jest to trudne).

Jedną z bardziej skomplikowanych operacji w trybie bezpołączeniowym jest stronicowanie. Właściwie nie ma dobrej metody, która pozwalałaby opracować schemat niezależny od schematu bazy. W ADO.NET 2.0 w beta 1 wprowadzono specjalny tryb DataReader, który pozwalał odczytywać kolejne strony (używał kursora po stronie serwera). Jednak ta funkcjonalność pojawi się dopiero w kolejnym VS.NET - ORCAS, który będzie dostępny po wprowadzeniu na rynek systemu Windows Vista.

Automatyczne powiadamianie o zmianach w bazie

Dzięki automatycznemu powiadamianiu o zmianach aplikacja kliencka może wiedzieć, czy dane po stronie serwera uległy zmianie. Dzięki temu można buforować obiekty będąc pewnym, że gdy się zmienią to albo dane w pamięci podręcznej zostaną uaktualnione, albo klient będzie w inny sposób poinformowany o tej sytuacji.

Załóżmy, że nasza aplikacja do wprowadzania faktur musi pozwalać użytkownikowi stale widzieć aktualny numer i datę ostatnio wprowadzonej faktury do systemu.

To pozornie proste zadanie w sytuacji, gdy nie zakładamy ścisłego powiązania pomiędzy klientem a bazą, wcale nie jest proste do osiągnięcia. Można oczywiście np. co 5 sekund odpytywać bazę danych "select top 1 * from Invoice order by Dt desc", ale proszę sobie wyobrazić obciążenie dla 10, 100 czy 1000 takich klientów, a wszystko po to by tylko na bieżąco informować o zmianie jakiejś wartości.

W SQL 2005 można do tego wykorzystać SQL Server Broker i Notification Services.

W .NET zdefiniowane są 3 poziomy API używającego tych serwisów. API na najniższym poziomie, to obiekty SqlNotificationRequest. Pozwalają one na pełną kontrolę nad działaniem serwera. W tym przykładzie użyjemy API wyższego poziomu - SqlDependency, które pozwala monitorować konkretne wyrażenia SQL i zgłasza w momencie zmiany odpowiednie zdarzenie. Jest jeszcze SqlCacheDependency - wyspecjalizowany obiekt wspierający działanie pamięci podręcznej w ASP.NET 2.0.

SqlDependency pozwala obserwować wynik generowany przez wyrażenie SQL. W momencie gdy ulegnie on zmianie, po stronie klienta zgłaszane jest zdarzenie. Technicznie rzecz biorąc, otwierany jest dodatkowy kanał komunikacyjny pomiędzy klientem a serwerem (podobnie jak w przypadku mechanizmu Remoting).

Inicjacja tego mechanizmu jest stosunkowo prosta. Najpierw trzeba zdefiniować monitorowane polecenie SqlCommand. Niestety, nie może to być dowolne wyrażenie - są problemy, gdy chce się użyć operatorów top, order itp. Najlepiej przyjąć, że ten mechanizm monitoruje konkretną tabelę - mogą to być wyrażenia typu select * from dbo.Invoice.

Potem należy zainicjować specjalny obiekt SqlDependency (określonym wyrażeniem) i wykonać dane wyrażenie, by Broker wiedział co jest monitorowane.

Aktywacja SQL Server Broker

Uwaga! Aby przykład zadziałał, konieczne jest odblokowanie dla danej bazy tej funkcjonalności - w tym wypadku wystarczy wydać polecenie:

ALTER DATABASE PCWKVS2005 SET ENABLE_BROKER

Domyślnie ta możliwość jest zablokowana dla każdej nowo utworzonej bazy.