Filozofia ADO.NET

ADO.NET może wydawać się dziwne programistom przyzwyczajonym do innych mechanizmów dostępu do danych. Ale to właśnie jego struktura narzuca odpowiednią organizację aplikacji i automatycznie wymusza dużą skalowalność pisanego systemu, a specjalny mechanizm DataSet pozwala zbudować efektywny bufor informacji po stronie aplikacji klienckiej.

ADO.NET może wydawać się dziwne programistom przyzwyczajonym do innych mechanizmów dostępu do danych. Ale to właśnie jego struktura narzuca odpowiednią organizację aplikacji i automatycznie wymusza dużą skalowalność pisanego systemu, a specjalny mechanizm DataSet pozwala zbudować efektywny bufor informacji po stronie aplikacji klienckiej.

ADO.NET jest podstawowym mechanizmem dostępu do danych używanym w aplikacjach wykorzystujących .NET Framework. Biblioteka ADO.NET ma zupełnie inną filozofię pracy niż ADO, JDBC, DAO czy nawet stare ODBC. Aby wyjaśnić różnicę, przyjmijmy, że mamy aplikację, która wyświetla formę z siatką, bazującą na jakiejś tabeli. Celem aplikacji jest pozwolenie użytkownikowi na pracę z danymi - np. zmianę wartości w niektórych wierszach.

W DAO/ADO czy ODCB proces wyglądał mniej więcej tak, że najpierw otwierany był kursor serwerowy (czyli - po stronie serwera powstawała dla danego klienta specjalna struktura obrazująca dane wybierane z tabeli), z którym związywana była siatka. W momencie gdy kontrolka siatki wykrywała zmiany, wysyłała informacje do kursora, z którym była związana, serwer wykrywał zmianę, mapował to na odpowiedni wiersz i pole, i uaktualniał bazową tabelę. Na przykład tak działa widok siatki w MS Access, gdy dane są pobierane z bazy SQL Server.

Klasyczna operacja wykonana w ADO.NET składa się z trochę większej liczby etapów (czasami określa się ją jako pracę w trybie odłączonym - disconnected):

  1. Najpierw tworzony jest obiekt (SqlConnection) - połączenie do SQL Server.

  2. Następnie tworzony jest obiekt (DataAdapter) do pobierania informacji z bazy, oparty na pewnym wyrażeniu (np. select * from Table1). Jego struktura może być wcześniej przygotowana.

  3. Przy użyciu obiektów DataAdapter i SqlConnection, wypełniana jest struktura pomocnicza przechowująca dane. Zwykle jest to DataSet - wyrafinowany komponent oparty na XML, który potrafi przechować historię zmian, sortować informacje w pamięci podręcznej, wyszukiwać, tworzyć widoki itp., praktycznie jest to baza danych, tyle że dostępna łatwo dla aplikacji klienckiej (oraz oczywiście warstwy środkowej).

    Po wypełnieniu obiektu pośredniego połączenie z bazą jest zamykane - nie jest potrzebne.

  4. Kontrolka dowiązywana jest do obiektu DataSet i to z nim pracuje użytkownik. Gdy dokonuje zmian w jakimś polu, DataSet przechowa wartość oryginalną i nową, ale nic nie jest wysyłane do bazy.

  5. Jeżeli zmiany mają być wysłane z powrotem na serwer, DataSet jest proszony o zwrócenie tych wartości, które zostały zmodyfikowane. DataAdapter zawiera specjalny składnik (polecenie), w którym można zdefiniować, w jaki sposób zmiany będą wysyłane na serwer (dokładniej są 3 takie elementy: dla dodawania nowych elementów, usuwania oraz do modyfikacji rekordów, które już istnieją). To od tych składników zależy, po czym będą identyfikowane rekordy (zwykle klucz główny) i jak będą rozwiązywane ewentualne konflikty (o tym dalej). Aby rozpocząć wysyłanie, ponownie otwierane jest połączenie do serwera bazodanowego a na podstawie zawartości DataSet DataAdapter generuje i wysyła ciąg poleceń. Po zakończeniu aktualizacji połączenie jest zamykane.

Takie rozwiązanie ma olbrzymią zaletę - serwer bazodanowy nie jest przesadnie obciążany każdym klientem. Także klient nie jest na sztywno powiązany z bazą danych - proszę zauważyć, że do operacji 4 (czyli pracy z DataSet) w ogóle nie jest potrzebne połączenie, można np. przenieść się poza zakres sieci bezprzewodowej z laptopem. W ten sposób stosunkowo prosto można napisać tzw. aplikację SmartClient, gdzie tak naprawdę serwer potrzebny jest tylko wtedy, gdy pobieramy dane albo wysyłamy aktualizację, a normalnie aplikacja pracuje w trybie offline.

Proszę zauważyć, że np. w ADO otworzenie kursora serwerowego zajmuje pewne zasoby i są one potrzebne przez cały czas gdy użytkownik ma otwartą formę (czyli nawet wtedy gdy użytkownik nie pracuje, lecz ma tylko włączoną aplikację). SmartClient pracuje w innym trybie: pobierz, odłącz się, pracuj wykorzystując pamięć podręczną (cache), a potem ewentualnie wyślij aktualizacje.

Oczywiście pojawia się cała gama problemów związanych z pracą równoległą, ale nie są one skomplikowane i tak naprawdę dzięki wsparciu ze strony .NET można je łatwo rozwiązać. Główny problem polega na tym, że jeżeli 2 użytkowników zmieni ten sam rekord, to nic nawzajem o sobie nie wiedzą, a więc mogą nadpisać swoje modyfikacje. W ADO.NET są stosowane 2 strategie: "ostatni ma rację", gdzie nie przejmujemy się ewentualnymi konfliktami (wbrew pozorom w wielu przypadkach można stosować taką strategię) oraz tzw. optymistyczna - gdzie zakładamy, że konflikty nie zdarzają się często (zwykle jest tak, że pracownicy operują na rozłącznych zbiorach danych - np. obsługują dokumenty danego klienta itp.). Wtedy wystarczy sprawdzić przed aktualizacją, czy rzeczywiście dane uległy zmianie.

Pule połączeń

Można by się zastanawiać, czy taki tryb pracy ma sens - bo przecież otwarcie połączenia chwilę trwa. Tak naprawdę przy pierwszym otwieraniu połączenia tworzona jest tzw. pula połączeń i późniejsze otwieranie/zamykanie to raczej pobieranie i zwracanie połączenia z puli. Proszę przyjrzeć się przykładowemu połączeniu:

Data Source=NIEZAPOMINAJKA2\SQL2005;

Initial Catalog=PCWKVS2005;

Integrated Security=True;

Pooling=True;

Connection Lifetime=100;

Max Pool Size=10;

Min Pool Size=5

Connection Lifetime określa czas istnienia połączenia w puli. 0 oznacza czas maksymalny - tzn. taki, na jaki pozwoli SQL Server (bo serwer może zamykać nieużywane połączenia).

Max Pool Size określa maksymalny rozmiar puli, Min Pool Size - minimalny rozmiar puli. Mechanizm działa w ten sposób, że przy pierwszym połączeniu tworzone jest co najmniej Min Pool Size połączeń. Jeżeli jakieś zostanie zamknięte przez serwer, to ta minimalna pula zostanie odtworzona. Potem każde połączenie przy użyciu tego samego ciągu (connection string) jest brane z tej puli.

Warto o tym pamiętać i np. nie ustawić w klasycznej aplikacji klient-serwer za dużej puli po stronie klienta, bo to będzie miało negatywny wpływ na działanie całego systemu.


Zobacz również