ADO.NET proste zapytania do bazy cz. 1

 

W kolejnej odsłonie zagadnień dotyczących ADO.NET chciałbym przedstawić proces pobierania danych z bazy. Na potrzeby omówienia tego zagadnienia wykorzystana została baza danych o nazwie ShoppingAppDb, która będzie służyć w nieodległej przyszłości jako główna baza dla tworzonej przeze mnie aplikacji.

W nawiązaniu do mojego poprzedniego posta, o zarządzaniu użytkownikami z poziomu ASP.NET, również w tej bazie danych prawa db_owner przyznane zostały użytkownikowi o nazwie AspNetUser. Hasło dla tego użytkownika to ‘password’. Opisane w tym poście czynności wykonane zostały na tabeli SimpleLogins specjalnie utworzonej do tego celu. Nie jest ona znormalizowana. Jej postać oraz dane w niej zawarte powstały jedynie w celu łatwej prezentacji zagadnienia, jakim jest pobieranie danych z bazy.

Tabela SimpleLogins utworzona została w następujacy sposób:

USE ShoppingAppDb;
GO

IF OBJECT_ID('SimpleLogins') IS NOT NULL
    DROP TABLE SimpleLogins;
    
CREATE TABLE SimpleLogins
(
    Id tinyint IDENTITY(1,1) not null PRIMARY KEY,
    FirstName varchar(20) null, 
    LastName varchar(20) null, 
    LoginName varchar(20) unique not null,
    Age int null,
    ExperienceLevel decimal(2,1) not null
)
GO

W powyższym fragmencie kodu, na początek sprawdzone zostaje czy tabela o danej nazwie już istnieje, jeśli tak, to jest ona usuwana. Zadaniem tabeli jest przechowywanie informacji o administratorach systemu. W kolumnie o nazwie Id założony został klucz podstawowy wraz z atrybutem IDENTITY(1, 1), co oznacza, że numeracja kolejnych identyfikatorów rozpoczyna się od 1, z krokiem zwiększającym tę wartość również o 1. Dane użytkownika takie jak imię i nazwisko nie zawsze są znane, aczkolwiek konieczne jest identyfikowanie się użytkownika loginem, czyli kolumna LoginName posiada atrybut unique. Oznacza to, że SQL Server ma zadbać o to, aby w tej kolumnie nie pojawiły się zduplikowane wartości. Wiek również nie musi być znany, a zatem już teraz zwracam uwagę na potencjalny problem, jakim jest fakt że zmienna typu int może przyjmować wartość NULL. Ostatnia kolumna, czyli ExperienceLevel, to wymyślony przez potencjalnego twórcę takiej bazy danych stopień doświadczenia danego administratora/użytkownika. Typem dla tej kolumny jest decimal(2, 1). Oznacza to, że przyjmowane będą wartości z przedziału 0.0-9.9, z krokiem równym 0.1. 

Tabela ta została uzupełniona następującymi danymi:

Id

FirstName

LastName

LoginName

Age

ExperienceLevel

1

NULL

NULL

Michal

18

2.2

3

Zbigniew

Kowalski

sa

NULL

2.2

Dla przypomnienia, wykorzystanie obiektów, których zadaniem jest wykonywanie operacji na bazie danych znajdującej się w SQL Server, wymaga dołączenia następujących przestrzenii nazw:

  • System.Data
  • System.Data.SqlClient

Na stronie wyświetlanej po uruchomieniu aplikacji wyświetlane są 4 opcje do wyboru za pomocą buttonów. Każdy z nich pokazuje inny aspekt pobierania danych z bazy, zatem dla ułatwienia i większej czytelności kodu, utworzenie connectionString-a przeniesione zostało do oddzielnej metody:

/// <summary>
/// Builds connection string in order to connect to ShoppingAppDb for specified user
/// </summary>
/// <returns>Connection string</returns>
private string BuildConnectionString()
{    
   string result = null;

   SqlConnectionStringBuilder strConn = new SqlConnectionStringBuilder();
   strConn.DataSource = @".\SQLEXPRESS";
   strConn.InitialCatalog = "ShoppingAppDb";
   strConn.UserID = "AspNetUser";
   strConn.Password = "password";
   result = strConn.ConnectionString;

   return result;
}

Proces tworzenia connectionStringa przy wykorzystaniu obiektu klasy SqlConnectionStringBuilder przedstawiłem w jednym z wcześniejszych postów na moim blogu.

Uwaga: Przedstawiony w dalszej części kod z racji ilości linii niezbędnych do zamieszczenia nie jest w 100% zabezpieczony, a jedynie poglądowo (np. nie są obsługiwane wyjątki, które wiem, że nie pojawią się w tym przykładzie).

Przykład 1:

Po kliknięciu w przycisk z tekstem “Get Login Information” pobierane z bazy danych są informacje o pierwszym użytkowniku, czyli posiadającym Id = 1. Następnie dane te wypisywane są na wyjście diagnostyczne:

protected void GetLoginButton_Click(object sender, EventArgs e)
{
    string strCon, strSql;
    strCon = BuildConnectionString();

    //Create new SqlConnection object using given string
    using (SqlConnection cn = new SqlConnection(strCon))
    {
        strSql = "SELECT Id, FirstName, LastName, LoginName FROM dbo.SimpleLogins WHERE Id=1";
          
        //Open the connection
        cn.Open();
        
        //Create SqlCommand object in order to query database
        using (SqlCommand cmd = new SqlCommand(strSql, cn))
        {
            try
            {
                //Use SqlDataReader to retrieve sequentially results
                using(SqlDataReader reader = cmd.ExecuteReader())
                {                        
                    while (reader.Read())
                    {
                        //Use column names to retrieve value
                        System.Diagnostics.Debug.WriteLine("Id: " + reader["Id"].ToString());
                        System.Diagnostics.Debug.WriteLine("FirstName: " + reader["FirstName"].ToString());
                        System.Diagnostics.Debug.WriteLine("LastName: " + reader["LastName"].ToString());
                        System.Diagnostics.Debug.WriteLine("LoginName: " + reader["LoginName"].ToString());
                    }
                    reader.Close();
                }
            }
            catch (Exception ex)
            {
                //implement
            }
        }
    }
}

Wykorzystanie obiektu typu SqlConnection już znamy z poprzedniego wprowadzenia. Przede wszystkim do wykonania zapytania musimy dysponować jego treścią, która ukryta jest w zmiennej strSql. Wysłanie zapytania do serwera (wykonywanego w taki właśnie sposób) wymaga również otwartego połączenia do bazy danych. Łatwo w ten sposób utworzyć obiekt typu SqlCommand, który odpowiedzialny jest za wykonywanie zapytań. W podanym przykładzie wykorzystany został konstruktor przyjmujący dwa argumenty: treść zapytania oraz obiekt typu SqlConnection odpowiedzialny za ustanowienie połączenia:

SqlCommand cmd = new SqlCommand(strSql, cn);

Obiekt SqlCommand zawiera w sobie jedynie zapytanie do bazy. Musi ono być wysłane do bazy, a wyniki poprawnie odebrane. Wysłanie zapytania, które zwraca więcej niż jedno pole, odbywa się za pomocą wywołania metody ExecuteReader() na obiekcie typu SqlCommand. Wyniki odbierane są poprzez przypisanie tego wywołania do obiektu typu SqlDataReader, który służy do przechowywania danych odczytywanych następnie sekwencyjnie. Obiekt ten ma również tę cechę, że raz odczytanych danych nie można ponownie odczytać, gdyż nie są one już dostępne.

SqlDataReader reader = cmd.ExecuteReader();

Następnie, w celu “zdjęcia” danych z takiego readera, należy wywołać na nim metodę Read(). Metoda ta, poza tym że pozwala na dostęp do jednego wiersza danych, to ponadto pozwala na ustalenie czy pozostały jeszcze jakieś dane do odczytania, zwracając wartość typu boolean.

Dostęp do pól odczytywanego wiersza możliwy jest poprzez wykorzystanie indeksów, zawierających nazwy odczytywanych kolumn, np:

System.Diagnostics.Debug.WriteLine("Id: " + reader["Id"].ToString());

Zaznaczyć należy, że taki sposób jest nieefektywny, gdyż pobranie w ten sposób wartości readera zwraca obiekt typu Object. Następuję więc operacja zwana boxingiem, a co za tym idzie musi potem nastąpić unboxing. Obie te operacje są niezmiernie kosztowne.

Na koniec należy zamknąć odczytywany strumień wywołując metodę Close(). Jest to niezbędne działanie, gdyż reader blokuje połączenie. Oznacza to, że na raz możliwe jest korzystanie tylko z jednego obiektu typu SqlDataReader:

reader.Close();

Po wykonanie zamieszczonego kodu w przykładzie otrzymujemy na wyjściu następujący wynik:

Id: 1

FirstName:

LastName:

LoginName: Michal

Widać więc, że pola zawierające wartości NULL po wykonaniu na obiektach metody ToString() nie są wyświetlane. Pozostałe dane odczytane zostały prawidłowo.

Kolejne przykłady, wykorzystujące bardziej efektywne metody odczytywania danych z bazy oraz problemy z tego tytułu wynikające, przedstawione zostaną w kolejnym poście.

Reklamy

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

%d blogerów lubi to: