Wykład 11 Model połączeniowy ADO.NET Obsługa połączenia - Ciąg połączeniowy - Pula połączeń Obiekt polecenia - Tworzenie - Wykonywanie - Wykonywanie procedur składowanych - Polecenia sparametryzowane Obiekt DataReader - Dostęp do wierszy - Dostęp do wartości kolumn 1
Model połączeniowy ADO.NET Obsługa połączenia Wybór klasy do obsługi połączenia zależy od dostawcy danych. Klasy: SqlConnection OracleConnection OleDbConnection OdbcConnection Każda klasa połączeniowa implementuje interfejs IDbConnection 2
Kategoria Nazwa Opis Właściwość ConnectionString Pobiera lub ustawia ciąg znaków służący do utworzenia połączenia ze źródłem danych. Właściwość ConnectionTimeout Liczba sekund, które należy czekać na ustanowienie połączenia ze źródłem danych przed przekroczeniem limitu czasu. Właściwość Database Nazwa bazy danych powiązanej z aktualnym połączeniem. Właściwość State Aktualny stan połączenia. Zwraca jedną z wartości wyliczenia ConnectionState: Broken, Closed, Connecting, Executing, Fetching lub Open. 3
Metoda Open() Close() Otwiera połączenie. Anuluje wszystkie bieżące operacje i zamyka połączenie, zwracając je do puli połączeń, jeśli taka istnieje. Metoda BeginTransaction() Rozpoczyna transakcję bazodanową. Metoda ChangeDatabase() Zmienia aktualną bazę danych otwartego połączenia. Nazwa nowej bazy danych przekazywana jest do metody w postaci ciągu znaków. Metoda CreateCommand() Tworzy obiekt polecenia wiązany z połączeniem. 4
Klasy połączenia związane z konkretnym dostawcą nie muszą udostępniać wszystkich funkcjonalności interfejsu IDbConnection. Ciąg połączeniowy Właściwość ConnectionString czyli ciąg znaków połączenia określa źródło danych i zawiera informacje niezbędne do uzyskania dostępu do źródła danych (np. hasło i nazwę użytkownika). Oprócz tych podstawowych informacji ciąg znaków może zawierać wartości pól specyficznych dla konkretnego dostawcy danych. Przykładowo ciąg znaków połączenia z SQL Server może zawierać wartości właściwości ConnectionTimeout (czas oczekiwania na połączenie) i PacketSize (rozmiar przesyłanych pakietów). Przykładowe ciągi połączeniowe przedstawia tabela. 5
Typ połączenia SqlConnection użycie identyfikacji SQL Server SqlConnection użycie identyfikacji Windows OleDbConnection łączenie z bazą Microsoft Access Przykład użycia server=mojserwer;uid=czynszeadmin;pwd=forsa;d atabase=czynsze; lub Data Source=MOJSERWER;UserID=czynszeadmin;Passwor d=forsa;initial Catalog=czynsze; server=mojserwer;database=czynsze;trusted Connection=yes; Provider=Microsoft.Jet.OLEDB.4.0;Data Source=e:\\nieruchomosci.mdb; w przypadku aplikacji sieciowych należy użyć metody MapPath() dla przekształcenia ścieżki wirtualnej na fizyczną string path=server.mappath( /data/nieruchomosci.mdb ); Data Source= +path+ ; 6
OdbcConnection DSN=czynsze Ciąg znaków połączenia można także utworzyć w bezpieczniejszy, obiektowy sposób, używając jednej z klas ConnectionStringBuilder udostępnianych przez zarządzanych dostawców danych. Pokazany dalej fragment kodu przedstawia sposób przypisania wartości składających się na ciąg znaków połączenia do właściwości danej klasy. Wewnętrznie obiekt tworzy ciąg znaków z tych właściwości i udostępnia go jako właściwość ConnectionString: Poszczególni dostawcy mają swoje wersje klasy ConnectionStringBuilder: SqlConnectionStringBuilder OleDbConnectionStringBuilder OracleConnectionStringBuilder ODBCConnectionStringBuilder 7
SqlConnectionStringBuilder sqlbldr = new SqlConnectionStringBuilder(); sqlbldr.datasource = "MOJSERWER"; // lub sqlbldr["datasource"] = "MOJSERWER"; sqlbldr.password = forsa"; sqlbldr.userid = czynszeadmin ; sqlbldr.initialcatalog = czynsze"; SqlConnection polaczenie = new SqlConnection(sqlBldr.ConnectionString); polaczenie.open(); 8
Obiekt wybranej klasy ConnectionStringBuilder jest szczególnie przydatny, kiedy ciąg znaków połączenia aplikacja pobiera z pliku konfiguracyjnego lub z innego źródła (np. z rejestru systemowego). Wybrane, najważniejsze właściwości przykładowej klasy SqlConnectionStringBuilder 9
Właściwość AsynchronousProcessing ConnectionTimeout DataSource MaxPoolSize MinPoolSize Password Pooling Opis Wartość logiczna, która określa, czy połączenie umożliwia przetwarzanie asynchroniczne. Za przesyłanie asynchronicznych poleceń odpowiada obiekt polecenia. Odpowiednik właściwości ConnectionTimeout obiektu Connection. Nazwa lub adres serwera SQL Server, z którym nawiązywane jest połączenie. Ustawia lub zwraca maksymalną i minimalną liczbę połączeń w puli połączeń konkretnego ciągu znaków połączenia. Hasło dostępu do konta SQL Server. Wartość logiczna określająca, czy wykorzystywana jest pula połączeń. UserID Nazwa użytkownika potrzebna do uzyskania dostępu do konta SQL Server. 10
Pula połączeń Tworzenie połączenia jest procesem czasochłonnym czasami trwa dłużej niż wykonanie innych poleceń. Aby wyeliminować ten narzut, ADO.NET tworzy pulę identycznych połączeń dla każdego niepowtarzalnego ciągu połączeniowego, którego żądanie otrzymuje. Umożliwia to obsługę przyszłych żądań danego ciągu znaków połączenia z puli. Nie wymaga to ponownego łączenia się z serwerem i wykonywania kosztownych zadań w celu sprawdzenia poprawności połączenia. 11
Zasady korzystania z puli połączeń: Pula połączeń jest domyślnie włączona. Można ją wyłączyć dla połączeń SqlConnection, dołączając do ciągu znaków połączenia instrukcję "Pooling=false". OleDbConnection wymaga dołączenia polecenia "OLE DB Services=-4". Każda pula połączeń jest powiązana z odrębnym ciągiem znaków połączenia. Kiedy aplikacja żąda połączenia, mechanizm obsługi puli porównuje ciąg znaków połączenia z tymi, które znajdują się w istniejących pulach. Jeśli pasuje do któregoś z nich, z odpowiedniej puli zostaje przydzielone połączenie. Jeśli w czasie zgłoszenia żądania wszystkie połączenia w puli są zajęte, żądanie zostaje umieszczone w kolejce żądań oczekujących na zwolnienie połączenia. Zwolnienie połączenia następuje po wywołaniu metody 12 Close() lub Dispose().
Pula połączeń zostaje zamknięta, kiedy nastąpi zwolnienie jej wszystkich połaczeń przez ich właścicieli i zostanie przekroczony limit czasu. W przypadku bazy danych SQL Server można kontrolować działanie puli połączeń, dodając pary klucz-wartość do ciągu znaków połączenia. Do ustawienia minimalnej i maksymalnej liczby połączeń w puli oraz do określenia, czy połączenie jest zerowane po pobraniu z puli, służą odpowiednie słowa kluczowe. Szczególnie istotne jest słowo kluczowe Lifetime, które określa czas życia połączenia. Ta wartość jest sprawdzana po zwróceniu połączenia do puli. Jeśli połączenie było otwarte dłużej, niż określa wartość Lifetime, to zostaje usunięte. 13
Przykład użycia odpowiednich słów kluczowych do obsługi obiektu SqlConnection: cnstring = "Server=MOJSERWER;Trusted_Connection=yes; + database=czynsze;connection reset=false;" + "connection Lifetime=60;" + // W sekundach + "min pool size=l;max pool size=50"; //Domyślnie 100 SqlConnection polaczenie = new SqlConnection(cnString); 14
Obiekt polecenia Po utworzeniu obiektu połączenia kolejny etap dostępu do bazy danych w modelu połączeniowym to utworzenie obiektu polecenia. Jego zadanie polega na przekazaniu do źródła danych zapytania lub polecenia działania. Klasy poleceń są udostępniane przez dostawców danych i muszą implementować interfejs IDbCommand. Do utworzenia obiektu polecenia można użyć bezpośrednio jednego z kilku konstruktorów lub wykorzystać klasę ProviderFactory. 15
Jak utworzyć obiekt polecenia i ustawić jego właściwości: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data.Common; using System.Data.Odbc; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace WindowsApplication1 { public partial class Form1 : Form { private String ds; private OdbcConnection polacz; private OdbcCommand polecenie; private String lancuch_polecenia; public Form1() { InitializeComponent(); odnumeru.focus(); //String ds; polacz = new OdbcConnection("DSN=ASA_Student_10;UID=dba;PWD=sql;"); polacz.open(); lancuch_polecenia = "select num_stud, nazwisko ' ' imie as nazim " + "from studenci where studenci.num_stud between " + 2014-11-07 14:54 ":od_num and :do_num Języki order... wykład by nazwisko, 11 imie"; 16
polecenie = new OdbcCommand(); polecenie.connection = polacz; polecenie.commandtext = lancuch_polecenia; } private void donumeru_leave(object sender, EventArgs e) { Int32 start = 0; Int32 stop = 1; lbstudenci.items.clear(); if (odnumeru.text.length == 0) { odnumeru.text = "0"; donumeru.text = "1"; } if (donumeru.text.length > 0) { start = Int32.Parse(odNumeru.Text.ToString()); stop = Int32.Parse(doNumeru.Text.ToString()); } polecenie = new OdbcCommand(); polecenie.connection = polacz; polecenie.commandtext = lancuch_polecenia; polecenie.parameters.addwithvalue("od_num", start); polecenie.parameters.addwithvalue("do_num", stop); OdbcDataReader dr = polecenie.executereader(commandbehavior.closeconnection); 17
while (dr.read()) { ds = dr.getstring(0) + " " + dr.getstring(1); lbstudenci.items.add(ds); } } private void button1_click(object sender, EventArgs e) { Application.Exit(); } } } 18
19
Jeżeli używa się różnych dostawców danych, to elastycznym rozwiązaniem jest wykorzystanie fabryki dostawców: string dostawca = "System.Data.Odbc"; DbProviderFactory fabryka = DbProviderFactories.GetFactory(dostawca); DbCommand rozkaz = fabryka.createcommand(); rozkaz.commandtext = lancuch_polecenia; rozkaz.connection = polacz; DbCommand jest klasą abstrakcyjną. Implementuje interfejs IDbCommand. Pełni ona rolę ogólnego obiektu polecenia. Eliminuje potrzebę rzutowania zwróconego obiektu polecenia na obiekt polecenia specyficzny dla dostawcy, na przykład na OdbcCommand. Rzutowanie takie jest konieczne, gdy potrzebny jest dostęp do niestandardowych właściwości klasy polecenia dostawcy. (np. metodę ExecuteXmlReader() udostępnia jedynie klasa SqlCommand. 20
Polecenie SQL zapisane we właściwości CommandText można wykonać wywołując jedną z metod z poniższej tabeli: Metoda ExecuteNonQuery () ExecuteReader() Opis i przykład Wykonuje działanie opisane w zapytaniu i zwraca liczbę zmienionych wierszy: cmd.commandtext = "DELETE from studenci WHERE nazwisko = Sielski "; int et = cmd.executenonquery(); Wykonuje zapytanie i zwraca obiekt DataReader, który umożliwia dostęp do zbioru wynikowego zapytania. Ta metoda przyjmuje opcjonalny obiekt CommandBehavior, który może poprawić wydajność wykonywania. cmd.commandtext="select * FROM studenci WHERE data_ur > 1985-12-04 ; SqlDataReader rdr = cmd.executereader(); 21
ExecuteScalar() ExecuteXmlReader() Wykonuje zapytanie i zwraca wartość pierwszej kolumny w pierwszym wierszu zbioru wynikowego jako wartość skalarną. cmd.commandtext= SELECT COUNT(*) FROM rejestr"; int ilekursow = (int)cmd.executescalar(); Dostępna tylko dla dostawcy danych SQL Server. Zwraca obiekt XmlReader, który pozwala uzyskać dostęp do zbioru wynikowego. 22
Spośród wymienionych metod najważniejsza to ExecuteReader(). Zwraca obiekt DataReader, który udostępnia wiersze zwrócone przez zapytanie. Przeciążona wersja tej metody przyjmuje parametr typu CommandBehavior. Można go wykorzystać (niektórzy dostawcy) do optymalizacji wykonywania zapytań. CommandBehavior jest typem wyliczeniowym o wartościach: SingleRow - określa, że zapytanie powinno zwrócić jeden wiersz. Domyślne działanie to zwracanie zbioru wynikowego z wieloma wierszami. SingleResult - zapytanie ma zwrócić pojedynczą wartość skalarną. KeyInfo - zwraca informacje o kolumnie i kluczu głównym. Ta wartość jest używana wraz z metodą GetSchema () obiektu DataReader do pobierania informacji o szablonie kolumny. 23
SchemaOnly - służy do pobierania nazw kolumn zbioru wynikowego, na przykład: dr = cmd.executereader(commandbehavior.schemaonly); String kolumna = dr.getname(o); //Nazwa pierwszej kolumny SequentialAccess - umożliwia sekwencyjny dostęp do zwróconych wierszy po poszczególnych kolumnach. Tej wartości używa się do obsługi dużych pól binarnych (BLOB) i pól tekstowych. CloseConnection - zamyka połączenie po zamknięciu obiektu DataReader. 24
Procedura składowana - kod SQL przechowywany w bazie danych, który można wykonać jako skrypt. Zalety: hermetyzacja, współdzielenie i powtórne wykorzystywanie tej samej logiki w różnych aplikacjach. ADO.NET umożliwia wykonywanie składowanych procedur dostawców danych OleDb, SqlClient, Odbc i OracleClient. Aby wykonać procedurę składowaną należy: - właściwości Sql.CommandText przypisać nazwę procedury, - ustawić właściwość CommandType na wartość wyliczeniową CommandType.StoredProcedure, - wywołać metodę ExecuteNonQuery(). 25
Jeśli składowana procedura ma parametry wejściowe lub wyjściowe, przed jej wykonaniem trzeba dodać odpowiednie parametry do kolekcji Parameters obiektu polecenia. string ciagpolaczenia = "dsn=asa_student_10;uid=dba;pwd=sql"; OdbcConnection polaczenie = new OdbcConnection(); polaczenie.connectionstring = ciagpolaczenia; polaczenie.open(); OdbcCommand rozkaz = new OdbcCommand("DopiszNowego", polaczenie); rozkaz.commandtype = CommandType.StoredProcedure; OdbcParameter param1 = new OdbcParameter(); param1.parametername = "nazw"; param1.odbctype = OdbcType.VarChar; param1.direction = ParameterDirection.Input; param1.value = "Rej"; rozkaz.parameters.add(param1); OdbcParameter param2 = new OdbcParameter(); param2.parametername = "im"; param2.odbctype = OdbcType.VarChar; param2.direction = ParameterDirection.Input; param2.value = "Mikolaj"; rozkaz.parameters.add(param2); ilewierszy = (int)rozkaz.executenonquery(); 26
CREATE PROCEDURE "DBA"."DopiszNowego"(in nazw Varchar(25), in im Varchar(25)) begin declare lit char(1); declare pl char(1); declare dt date; select "max"(data_ur) into dt from Studenci where plec in('k','k'); set lit=substr(trim(im),length(trim(im)),1); if lit='a' or lit='a' then set pl='k' else set pl='m' end if; insert into Studenci(nazwisko,imie,plec,data_ur) values(nazw,im,pl,dt+10) end 27
Do utworzenia polecenia SQL dopisującego nowego studenta do tabeli Studenci przykładowej bazy Student można użyć sparametryzowanego polecenia: string sql = "insert into studenci (nazwisko, imie, data_ur, adres, plec) values(:pnazw, :Pim, :Pdat_ur, :Padr, :Ppl)"; OdbcCommand cmd = new OdbcCommand(); cmd.commandtext = sql; //Parametry określają zapisywane wartości cmd.parameters.addwithvalue( Pnazw", Schindler"); cmd.parameters.addwithvalue( Pim", Johann"); cmd.parameters.addwithvalue( Pdat_ur", 1915-10-03"); cmd.parameters.addwithvalue( Padr, Kraków ); cmd.parameters.addwithvalue( Ppl, M ); 28
Alternatywnym rozwiązaniem jest utworzenie polecenia z połączonych ciągów znaków: string nazw = Schindler ; string imie = Johann ; string data_ur = 1915-10-03 ; string adres = Kraków ; string pl = M ; string sql = "insert into studenci (nazwisko, imie, data_ur, adres, plec) values ; sql += ( + nazw +, + imie + 29
Obiekt DataReader Obiekt DataReader, udostępnia wiersze i kolumny danych zwrócone jako wynik przetworzenia zapytania. Dostęp do wierszy definiuje interfejs IDataReader, który musi implementować każdy obiekt DataReader. Dostęp do kolumn definiuje interfejs IDataRecord. Każde wywołanie metody Read() obiektu DataReader zwraca pojedynczy wiersz ze zbioru wynikowego. Jeśli w zbiorze nie ma już więcej wierszy, metoda zwraca false. Obiekt DataReader należy zamknąć po zakończeniu przetwarzania wiersza w celu zwolnienia zasobów systemowych. Do sprawdzenia, czy ten obiekt jest zamknięty, służy właściwość DataReader. IsClosed. 30
Mimo że DataReader jest powiązany z pojedynczym poleceniem, to polecenie może zawierać wiele zapytań zwracających wiele zbiorów wynikowych. Przykładowy fragment kodu pokazuje, jak DataReader przetwarza wiersze zwrócone przez dwa zapytania: string ql = "SELECT * FROM studenci WHERE YEAR(data_ur)< 1940"; string q2 = "SELECT * FROM studenci WHERE YEAR(data_ur)> 1980 ; cmd.commandtext = ql + ";" + q2; DbDataReader czytacz = cmd.executereader(); bool nastepny = true; while (nastepny) { while (czytacz.read()) { MessageBox.Show(czytacz.GetString(1)); { nastepny = czytacz.nextresult(); } czytacz.close(); conn.close(); 31
Jest wiele sposobów na dostęp do danych w kolumnach danego wiersza obiektu DataReader. Można użyć numeru kolumny (pierwszy wiersz ma numer zero), ponieważ jest to tablica, nazwy jako indeksu, metody GetValue(), przekazując do niej numer kolumny lub jednej ze ściśle typizowanych metod Getxxx(), do których należą GetString(), Getlnt32(), GetDateTime() i GetDouble(). 32