Wykład 6 ADO model bezpołączeniowy Klasa DataSet kolekcja DataTable Obiekty DataColumn Obiekty DataRow Aktualizacja bazy za pomocą obiektu DataAdapter Definiowanie powiązań między tabelami w obiekcie DataSet Wiązanie komponentów z danymi Wiele formularzy aplikacji Model połączeniowy czy bezpołączeniowy? 1
ADO.NET model bezpołączeniowy Model bezpołączeniowy wykorzystuje obiekt DataSet jako pamięć podręczną. Obiekt DataAdapter służy jako pośrednik pomiędzy obiektem DataSet a źródłem danych, z którego pochodzą dane w pamięci. Po załadowaniu danych DataAdapter zwraca obiekt połączenia do puli, odłączając w ten sposób dane od źródła danych. DataAdapter jest klasą osłonową wokół obiektów DataReader dostawców, które wykonują rzeczywiste ładowanie danych. 2
3
Klasa DataSet Obiekt klasy DataSet pełni rolę bazy danych w pamięci. Jego właściwość Tables udostępnia kolekcję obiektów DataTables, które zawierają dane i opisujące je szablony. Właściwość Relations zwraca kolekcję obiektów DataRelation, które definiują powiązania między tabelami. Metody Copy(), Merge() i Clear() klasy DataSet odpowiadają za kopiowanie, scalanie i usuwanie zawartości obiektów tej klasy. DataSet i DataTable to podstawowe elementy architektury ADO.NET i w przeciwieństwie do klas Connection, DataReader i DataAdapter nie są powiązane z konkretnym dostawcą danych. 4
Aplikacja może tworzyć, definiować i zapełniać obiekty DataSet pochodzące z dowolnego źródła danych. Hierarchia klasy DataSet 5
Klasa PropertyCollection jest zbiorem niestandardowych właściwości przechowywanych i udostępnianych jako wartość właściwości DataSet.ExtendedProperties. Obiekty tej klasy służą do przechowywania oznaczeń czasowych lub opisowych informacji, na przykład wymagań dotyczących sprawdzania poprawności kolumn w tabelach zbioru danych. Kolekcja DataTable Kolekcja obiektów DataTable (klasa DataTableCollection) jest dostępna za pośrednictwem właściwości DataSet.Tables. Obiekty tej klasy przechowują dane w formacie wiersz - kolumna (analogicznie jak tabele relacyjnych baz danych). Dzięki swoim właściwościom i metodom może stanowić samodzielne źródło danych lub część kolekcji tabel w obiekcie DataSet. Najważniejsze właściwości tej klasy Columns i Rows definiują układ i zawartość tabeli. 6
Obiekty DataColumn Właściwość DataTable.Columns udostępnia kolekcję obiektów DataColumn, które reprezentują wszystkie pola danych obiektu DataTable. Właściwości kolumn zebrane razem tworzą szablon danych tabeli. Właściwość ColumnName DataType MaxLength AllowDBNull ReadOnly Unique Expression Caption DataTable Opis Nazwa kolumny Typ danych przechowywanych w kolumnie Przykład: col1.datatype = System.Type.GetType("System.String") Maksymalna długość kolumny tekstowej (-1 jeżeli maksymalna długość jest nieokreślona) Określa, czy kolumna może zawierać wartości NULL Określa czy można modyfikować zawartość kolumny Określa, czy kolumna musi zawierać unikalne wartości Wyrażenie definiujące sposób obliczania wartości kolumny Przykład: colvat.expression = colnetto*0.22 Nagłówek wyświetlany w interfejsie użytkownika Nazwa obiektu DataTable zawierającego daną kolumnę 7
Kolumny obiektu DataTable powstają automatycznie w czasie zapełniania tabeli wynikami zapytania do bazy danych lub w wyniku odczytu danych z pliku XML. W przypadku kiedy aplikacja zapełnia tabele dynamicznie (na podstawie danych podawanych przez użytkownika lub pobieranych w czasie rzeczywistym), konieczne może być napisanie kodu definiującego strukturę tabeli. Przykład przedstawia fragment kodu, który tworzy obiekt DataTable, obiekty DataColumn, przypisuje wartości do kolumn, a następnie dodaje te kolumny do obiektu DataTable. Kod zawiera również definicję kolumny wyliczanej. 8
9
10
11
12
13
14
15
16
Obiekty DataRow Dane są dodawane do tabeli po utworzeniu nowego obiektu DataRow, zapełnieniu go danymi w kolumnach i dodawanie go do kolekcji Rows tabeli. Fragment kodu przedstawia przykład umieszczania danych w tabeli utworzonej w poprzednim przykładzie. 17
Klasa DataTable ma metody, które umożliwiają zatwierdzanie i anulowanie zmian danych w tabeli. Obiekty tej klasy przechowują stan każdego wiersza we właściwości DataRow.RowState. Może ona przyjmować jedną z pięciu wartości wyliczenia DataRowState: Added, Deleted, Detached, Modified lub Unchanged. W powyższym przykładzie pokazano, jak ustawiać te wartości. 18
Metody AcceptChanges() i RejectChanges() klasy DataTable to odpowiedniki zatwierdzania i anulowania operacji w bazie danych. Metody te dotyczą wszystkich zmian wprowadzonych od czasu załadowania tabeli lub poprzedniego wywołania metody AcceptChanges(). W pokazanym przykładzie można było przywrócić usunięty wiersz, ponieważ program nie zatwierdził usunięcia przed wywołaniem metody RejectChanges(). Zmiany są wprowadzane w obiekcie DataTable, a nie w oryginalnym źródle danych. Dla każdej kolumny wiersza ADO.NET przechowuje oryginalną i aktualną wartość. Po wywołaniu metody RejectChanges() aktualne wartości są zmieniane na oryginalne wartości. W przypadku wywołania metody AcceptChanges() wartości oryginalne zostają przekształcone na wartości aktualne. Dostęp do dwóch zbiorów wartości (przed i po zatwierdzeniu zmian) można uzyskać za pomocą wartości wyliczeniowych Current i Original właściwości DataRowVersion: 19
Aktualizacja bazy za pomocą obiektu DataAdapter Po zakończeniu ładowania danych przez obiekt DataAdapter, służące do tego połączenie zostaje zamknięte. Zmiany w danych są odzwierciedlane jedynie w obiekcie DataSet, a nie w oryginalnym źródle danych. Aby wprowadzić te zmiany do źródła danych, należy użyć obiektu DataAdapter w celu odtworzenia połączenia i przesłania zmienionych wierszy do oryginalnej bazy danych. Do wykonania tego zadania można użyć tego samego obiektu DataAdapter, który służył do zapełnienia obiektu DataSet. 20
Klasa DataAdapter ma trzy właściwości przypisane do określonych poleceń SQL, które wykonują zadania odpowiadające nazwom tych właściwości. InsertCommand (wstawianie), DeleteCommand (usuwanie), UpdateCommand (aktualizacja). Te polecenia są wykonywane po wywołaniu metody Update()obiektu DataAdapter. Trudność polega na utworzeniu poleceń SQL, które wysyłają zmiany, i przypisaniu ich do odpowiednich właściwości obiektu DataAdapter. Wszyscy dostawcy danych implementują klasę CommandBuilder, której można użyć do automatycznej obsługi tych operacji. 21
22
Obiekt CommandBuilder generuje polecenia niezbędne do aktualizacji źródła danych po wprowadzeniu zmian w obiekcie DataSet. Można utworzyć obiekt klasy CommandBuider, przekazując do konstruktora odpowiedni obiekt DataAdapter. Po wywołaniu metody DataAdapter.Update(), system wygeneruje i wykonana polecenie SQL. Przykłady: DataTable tabela = ds.tables["studenci"]; // (1) Obiektu CommandBuilder używamy do generowania poleceń // aktualizacji SqlCommandBuilder sb = new SqlCommandBuilder(da); // (2) Dodawanie studenta do tabeli DataRow wiersz = tabela.newrow(); wiersz["nazwisko"] = "Koniecpolski"; wiersz["imie"] = "Krzysztof"; wiersz["data_ur ] = "1567-11-23 ; Wiersz["plec ] = "M ; tabela.rows.add(drow); 23
// (3) Usuwanie wiersza z tabeli tabela.rows[4].delete(); // (4) Zmiana wartości kolumny tabela.rows[5]["data_ur"] = "1944"; // (5) Aktualizacja oryginalnej tabeli w bazie źródłowej (na serwerze) int updates = da.update(ds, "studenci"); MessageBox.Show("Zmienione wiersze: " + updates.tostring(); Jest kilka ograniczeń, o których trzeba pamiętać, korzystając z obiektu CommandBuilder. Polecenie Select powiązane z obiektem DataAdapter musi dotyczyć pojedynczej tabeli, a tabela źródłowa w bazie danych musi zawierać klucz główny lub kolumnę zawierającą niepowtarzalne wartości. Ta kolumna (lub kolumny) musi znaleźć się w oryginalnym poleceniu Select. 24
Zastosowanie obiektu DataAdapter upraszcza i automatyzuje proces aktualizacji bazy danych (dowolnego innego magazynu danych). Występują jednak problemy z aktualizacjami wielu użytkowników (współbieżne przetwarzanie transakcji). Model bezpołączeniowy wykorzystuje zasadę optymistycznej równoległości. Wiersze oryginalnego źródła danych nie są blokowane między odczytem a aktualizacją. W czasie tej przerwy inny użytkownik może zaktualizować źródło danych. Metoda Update() rozpoznaje, czy po ostatnim odczycie miały miejsce jakieś zmiany i nie aktualizuje zmienionych wierszy. Są dwie podstawowe techniki radzenia sobie z błędami przetwarzania równoległego, powstającymi przy zatwierdzaniu wielu aktualizacji. anulowanie wszystkich zmian w przypadku naruszenia zasad zatwierdzenie aktualizacji, które nie powodują błędów i wykrycie tych, które powodują, dzięki czemu można je ponownie przetworzyć. 25
Jeśli właściwość DataAdapter.ContinueUpdateOnError ma wartość false, to wówczas, jeżeli aktualizacja wiersza nie jest możliwa, program zgłasza wyjątek. Zapobiega to wprowadzaniu kolejnych aktualizacji, ale nie wpływa na aktualizacje sprzed zgłoszenia wyjątku. Ponieważ aktualizacje mogą zależeć od siebie, aplikacje często wymagają zastosowania techniki "wszystko albo nic". Najłatwiejszy sposób na jej wdrożenie to utworzenie transakcji, w której wykonywane są wszystkie polecenia aktualizacji. W tym celu należy utworzyć obiekt SqlTransaction i przekazać do niego polecenie SqlDataAdapter.SelectCommand, co wiąże to polecenie z transakcją. Jeśli program zgłosi wyjątek, można użyć metody Rollback() do anulowania wszystkich zmian. Jeśli wszystkie operacje się powiodą, program wykona metodę Commit() zatwierdzającą wszystkie polecenia aktualizacji. 26
Przykłady aplikacji z modyfikacją danych w bazie: 27
28
29
30
31
32
33
34
35
36
37
38
Tworzenie połączenia ze źródłową bazą danych i wiązanie kontrolek z danymi - przykład: 39
40
41
42
43
44
Plik app.config -zawiera łańcuch połączeniowy; notacja XML <?xml version="1.0" encoding="utf-8"?> <configuration> <configsections> </configsections> <connectionstrings> <add name="łańcuch_połaczeniowy.properties.settings.baza_dyplomyconnectionstring" connectionstring="data Source=TEJOT0\MSSQLSERV_2016;Initial Catalog=Baza_dyplomy;Integrated Security=True" providername="system.data.sqlclient" /> </connectionstrings> <startup> <supportedruntime version="v4.0" sku=".netframework,version=v4.5.2" /> </startup> </configuration> 45
46
47
48
49
50
51
Wiele formularzy aplikacji 52
53
54
55
56
Plik app.config -zawiera łańcuchy połączeniowe z dwoma różnymi bazami 57
58
59
60
61
62
63
64
65
66
Model połączeniowy czy bezpołączeniowy? Klasa DataReader pozwala na dostęp sekwencyjny w trybie tylko do odczytu. Przetwarzanie po jednym wierszu minimalizuje wymagania związane z pamięcią. Klasa DataSet umożliwia dostęp do danych do odczytu i do zapisu, ale wymaga wystarczającej ilości pamięci, aby pomieścić kopię danych pobranych ze źródła danych. Jeśli aplikacja nie wymaga aktualizowania źródła danych i służy jedynie do wyświetlania i wyboru danych, lepiej zastosować klasę DataReader. Jeśli aplikacja ma aktualizować dane, należy użyć klasy DataSet. Jeśli źródło danych zawiera dużą liczbę rekordów, obiekt DataSet może wymagać zbyt wielu zasobów. Jeśli dane wymagają niewielkiej liczby zmian, lepiej wykorzystać obiekty DataReader i Command do wykonywania aktualizacji. 67
Klasa DataSet stanowi dobry wybór w następujących sytuacjach: Dane są serializowane lub przesyłane przez łącza za pomocą HTTP. Formularz zawiera wiele powiązanych ze źródłem danych kontrolek tylko do odczytu. Kontrolki formularza, jak GridView lub DataView, są powiązane ze źródłem danych, które można aktualizować. Aplikacja musi zmieniać, dodawać lub usuwać wiersze danych. Klasę DataReader lepiej stosować wtedy gdy: - Potrzebna jest obsługa dużej liczby rekordów, przez co wymagania związane z pamięcią i długi czas ładowania powodują, że klasa DataSet jest nieprzydatna. - Dane są tylko do odczytu i są powiązane z kontrolką listy formularza Windows lub Web. - Baza danych podlega częstym zmianom, przez co zawartość obiektu DataSet wymagałaby częstych aktualizacji. 68