Serwisanci dostępni teraz · czynne całą dobę, także w weekendy i świętaWarszawa i okolice · dojazd bezpłatny
Darmowa diagnoza laptopa
Darmowa diagnoza laptopa
Dojazd do klienta bezpłatnie
Dojazd do klienta bezpłatnie
Laptopy zastępcze bezpłatnie
Laptopy zastępcze bezpłatnie
Odbiór i zwrot sprzętu bezpłatnie
Odbiór i zwrot sprzętu bezpłatnie

SqlDataReader

Termin Definicja
SqlDataReader

SqlDataReader to klasa .NET umożliwiająca szybki, jednokierunkowy odczyt wyników zapytania.

SqlDataReader jako strumieniowy odczyt danych

SqlDataReader jest elementem platformy .NET używanym do odczytywania rezultatów zapytań kierowanych do bazy Microsoft SQL Server. Jego działanie opiera się na odczycie sekwencyjnym: po wykonaniu polecenia można przechodzić przez kolejne rekordy w ustalonej kolejności, bez pobierania całego wyniku do pamięci aplikacji. Takie podejście dobrze sprawdza się wtedy, gdy zapytanie zwraca wiele wierszy, a dane mają być przetwarzane na bieżąco, na przykład podczas tworzenia listy, generowania zestawienia albo przekazywania informacji do kolejnego etapu programu.

Obiekt tego typu jest zwykle tworzony przez połączenie z bazą danych oraz polecenie SQL. Po uruchomieniu polecenia otrzymuje się czytnik, który należy otworzyć, a następnie wywoływać metodę Read(). Każde prawidłowe przejście przesuwa bieżącą pozycję na następny rekord. Dane z aktualnego wiersza można odczytywać według nazwy kolumny albo jej indeksu. Po zakończeniu pracy czytnik powinien zostać zamknięty, podobnie jak użyte połączenie i polecenie. W praktycznym kodzie często stosuje się konstrukcję using, ponieważ ułatwia ona zwolnienie zasobów także wtedy, gdy podczas odczytu wystąpi błąd.

SqlDataReader nie jest kolekcją przechowującą wynik w formie swobodnie dostępnej tabeli. Po przejściu do następnego rekordu nie można traktować poprzedniego wiersza jak elementu listy, do którego da się wrócić w dowolnym momencie. Z tego powodu dane powinny być od razu mapowane do obiektu, przetwarzane, zapisywane w innym miejscu albo przekazywane do warstwy prezentacji.

SqlDataReader a inne sposoby pobierania wyników

Najważniejszą cechą SqlDataReader jest jednokierunkowy charakter pracy. W porównaniu z mechanizmem, który tworzy w pamięci pełny zbiór wyników, czytnik zużywa mniej pamięci, ponieważ nie musi przechowywać wszystkich rekordów naraz. Ma to znaczenie przy większych zapytaniach, szczególnie gdy pojedynczy wiersz zawiera wiele pól albo dane są wykorzystywane stopniowo. Zyskiem może być również krótszy czas rozpoczęcia przetwarzania, ponieważ pierwszy rekord jest dostępny bez oczekiwania na zbudowanie kompletnej struktury po stronie aplikacji.

Korzyść ta wiąże się jednak z ograniczeniami. Odczyt pozostaje powiązany z aktywnym połączeniem z bazą, dlatego połączenie nie powinno być zamykane przed zakończeniem przechodzenia przez rekordy. W czasie działania czytnika zasoby po stronie serwera oraz aplikacji są zajęte. Jeżeli potrzebne jest wielokrotne filtrowanie, sortowanie, przeskakiwanie między wierszami albo praca bez połączenia, lepszym rozwiązaniem może być wcześniejsze zapisanie danych do listy, tabeli danych lub własnych obiektów.

W aplikacjach wykorzystujących programowanie asynchroniczne dostępne są także warianty odczytu niewstrzymujące głównego wątku. W takim modelu stosuje się między innymi ExecuteReaderAsync() oraz ReadAsync(). Nie zmienia to podstawowej zasady, zgodnie z którą wynik jest odczytywany po kolei. Asynchroniczność pozwala natomiast lepiej obsługiwać oczekiwanie na odpowiedź bazy w aplikacjach internetowych, okienkowych i usługach, w których blokowanie wątku mogłoby pogorszyć płynność działania.

Podczas projektowania zapytań należy również pamiętać o różnicy między parametrami a łączeniem tekstu. Parametryzowanie poleceń ułatwia bezpieczne przekazywanie wartości i ogranicza ryzyko nieprawidłowej interpretacji danych wejściowych. SqlDataReader odpowiada za odczyt rezultatu, ale nie zastępuje poprawnego budowania samego zapytania.

SqlDataReader i typowe problemy w kodzie

Jednym z częstych błędów jest próba odczytania danych przed wykonaniem Read(). Po utworzeniu czytnika bieżący wiersz nie jest jeszcze ustawiony, więc odwołanie do kolumny w tym momencie może zakończyć się wyjątkiem. Poprawny schemat zakłada sprawdzenie wartości zwracanej przez Read() i pobieranie pól dopiero wewnątrz pętli albo po potwierdzeniu istnienia pierwszego rekordu.

Problematyczne bywają także wartości NULL z bazy. Nie należy zakładać, że każda kolumna zawiera wartość zgodną z typem prostym w kodzie. Przed rzutowaniem można sprawdzić IsDBNull(), a następnie przypisać wartość domyślną, użyć typu nullable lub zastosować własną regułę mapowania. Brak takiego sprawdzenia może prowadzić do wyjątków podczas konwersji, zwłaszcza w przypadku dat, liczb oraz pól opcjonalnych.

Ostrożności wymaga również odczyt kolumn według indeksu. Zmiana kolejności pól w zapytaniu może wtedy spowodować przypisanie danych do niewłaściwych właściwości. Odczyt po nazwie jest bardziej czytelny i odporny na część takich zmian, choć może wymagać ustalenia nazw w sposób zgodny z rzeczywistym schematem zapytania. Przy częstym odczycie tych samych kolumn przydatne może być jednorazowe pobranie ich indeksów za pomocą GetOrdinal().

Inny błąd polega na przetrzymywaniu czytnika zbyt długo. Jeżeli w trakcie iterowania wykonywane są dodatkowe, czasochłonne operacje, połączenie z bazą pozostaje zajęte przez cały ten okres. W razie potrzeby można ograniczyć zakres danych zapytaniem, pobierać tylko wymagane kolumny i szybko zamieniać rekordy na własne obiekty. Należy także unikać pobierania dużych pól, których aplikacja faktycznie nie wykorzystuje.

Nie powinno się ignorować wyjątków związanych z połączeniem, przerwaniem zapytania, niezgodnością typów ani zamknięciem zasobu. Obsługa błędów powinna pozwalać na zapisanie informacji diagnostycznych bez ujawniania wrażliwych danych użytkownika. W przypadku pracy z transakcjami znaczenie ma również kolejność zamykania czytnika i kończenia transakcji, ponieważ otwarty odczyt może uniemożliwić dalsze działania na tym samym połączeniu.

SqlDataReader w praktycznym użyciu

Przed rozpoczęciem odczytu warto ograniczyć wynik już na poziomie SQL. Selekcja konkretnych kolumn, zastosowanie właściwego warunku oraz pobieranie danych w logicznych partiach zmniejsza ilość informacji przesyłanych między bazą a aplikacją. Jeżeli wynik może być bardzo duży, przydatne bywa porcjowanie danych z użyciem odpowiedniej klauzuli, sortowania po stabilnym kluczu albo mechanizmu stronicowania dopasowanego do wersji silnika bazy.

Mapowanie rekordu powinno być jednoznaczne. Dobrą praktyką jest wydzielenie fragmentu odpowiedzialnego za przekształcenie bieżącego wiersza w obiekt domenowy lub model transportowy. Dzięki temu logika związana z typami, wartościami pustymi i nazwami kolumn nie miesza się z obsługą interfejsu. Przydatne są metody takie jak GetString(), GetInt32(), GetDateTime() czy GetFieldValue<T>(), ponieważ jasno wskazują oczekiwany typ danych. Ich użycie powinno być poprzedzone sprawdzeniem, czy kolumna nie zawiera wartości pustej oraz czy jej typ jest zgodny ze schematem.

Warto rozdzielić czas oczekiwania na bazę od czasu przetwarzania rekordu. Jeżeli pomiar obejmuje oba etapy jednocześnie, trudniej ustalić, czy przyczyną spowolnienia jest zapytanie, transfer danych, konwersja typów, zapis wyniku czy działanie interfejsu. Przydatne może być rejestrowanie czasu wykonania polecenia, liczby odczytanych wierszy oraz informacji o błędach. Dane diagnostyczne powinny być pozbawione haseł, pełnych danych osobowych i innych informacji, które nie są potrzebne do analizy.

Przy pracy z dużymi tekstami lub obiektami binarnymi trzeba uwzględnić sposób ich pobierania. W niektórych zastosowaniach korzystniejsze jest odczytywanie fragmentów, aby nie tworzyć w pamięci aplikacji nadmiernych buforów. W innych przypadkach lepszym rozwiązaniem będzie przechowywanie plików poza tabelą i zapisywanie w bazie jedynie metadanych. Decyzję należy oprzeć na rozmiarze danych, sposobie dostępu, wymaganiach kopii zapasowych i oczekiwanej wydajności.

SqlDataReader i moment zgłoszenia problemu

Wsparcie techniczne może być potrzebne, gdy aplikacja zaczyna zużywać dużo pamięci mimo korzystania z czytnika, połączenia pozostają otwarte, zapytania wykonują się coraz dłużej albo pojawiają się błędy dotyczące zamkniętych zasobów. Zgłoszenie jest również zasadne wtedy, gdy po zmianie struktury tabeli dane trafiają do nieprawidłowych pól, wartości puste powodują wyjątki lub aplikacja działa poprawnie tylko dla małych wyników.

W ramach Warszawskiego Pogotowia Komputerowego 24/7 można skonsultować problemy z działaniem komputera, aplikacji oraz połączeń wykorzystywanych przy pracy z bazą danych. Do przygotowania diagnozy pomocne są komunikat błędu, fragment logu bez danych poufnych, informacja o wersji środowiska .NET, opis wykonywanego zapytania oraz wskazanie, czy problem występuje stale, czy tylko przy większej liczbie rekordów. Takie materiały ułatwiają odróżnienie błędu w kodzie od problemu z konfiguracją, zasobami systemu lub samą bazą.

Przed przekazaniem sprzętu lub środowiska do analizy warto wykonać kopię ważnych danych i zanotować ostatnie zmiany, takie jak aktualizacja systemu, sterowników, aplikacji albo konfiguracji połączenia. Nie należy przekazywać haseł zapisanych w kodzie ani plików zawierających pełne dane klientów. W wielu przypadkach wystarcza bezpiecznie przygotowany opis, zanonimizowany fragment projektu oraz powtarzalny przykład problemu. Dzięki temu można szybciej ustalić, czy konieczna jest korekta obsługi SqlDataReader, zmiana zapytania, poprawa zwalniania zasobów czy szersza diagnostyka komputera.

+48 22 100 48 29Zadzwoń teraz · serwisant w 1 godzinę