Wykład 12. ADO.NET model bezpołączeniowy



Podobne dokumenty
Bazy danych 2. Wykład 6

Architektura ADO.NET Dostawcy danych Modele dostępu do danych model połączeniowy Model bezpołączeniowy

Wykład 4. Architektura ADO.NET Dostawcy danych Modele dostępu do danych model połączeniowy Model bezpołączeniowy. Bazy danych 2

Platforma.NET laboratorium 4 Aktualizacja: 15/11/2013. Visual Basic.NET dostęp do bazy danych. Baza Microsoft SQL Server Compact

Programowanie w MS Visual Studio 2005 z wykorzystaniem MS SQL Server 2005

Wykład 10 Architektura ADO.NET Dostawcy danych Modele dostępu do danych model połączeniowy Model bezpołączeniowy

ZAPOZNANIE SIĘ ZE SPOSOBEM PRZECHOWYWANIA

Projektowanie aplikacji z bazami danych

Plan. Formularz i jego typy. Tworzenie formularza. Co to jest formularz? Typy formularzy Tworzenie prostego formularza Budowa prostego formularza

PHP może zostać rozszerzony o mechanizmy dostępu do różnych baz danych:

ADO.NET. Obiektowy dostęp do danych. Przygotował Jakub Światły

1 LINQ. Zaawansowane programowanie internetowe Instrukcja nr 1

Autor: Joanna Karwowska

z użyciem kontrolek ASP.NET

Programowanie w MS Visual Studio 2005 z wykorzystaniem MS SQL Server 2005

Projekt Hurtownia, realizacja rejestracji dostaw produktów

Laboratorium nr 4. Temat: SQL część II. Polecenia DML

Język SQL. Rozdział 9. Język definiowania danych DDL, część 2.

Budowa aplikacji ASP.NET współpracującej z bazą dany do obsługi przesyłania wiadomości

Programowanie obiektowe i zdarzeniowe wykład 4 Kompozycja, kolekcje, wiązanie danych

Kurs programowania aplikacji bazodanowych

Projektowanie aplikacji bazodanowych w.net

Projekt Hurtownia, realizacja rejestracji dostaw produktów

Podstawy technologii WWW

System zarządzania firmą specyfikacja techniczna.

Wykład 12. Programowanie serwera MS SQL 2005 w C#

PHP: bazy danych, SQL, AJAX i JSON

Metody dostępu do danych

Wykład 8. SQL praca z tabelami 5

Języki programowania wysokiego poziomu. PHP cz.4. Bazy danych

Obiektowy PHP. Czym jest obiekt? Definicja klasy. Składowe klasy pola i metody

Podstawowe pojęcia dotyczące relacyjnych baz danych. mgr inż. Krzysztof Szałajko

Baza danych sql. 1. Wprowadzenie

koledzy, Jan, Nowak, ul. Niecała 8/23, , Wrocław, , ,

Oracle PL/SQL. Paweł Rajba.

Oracle11g: Wprowadzenie do SQL

Laboratorium Technologii Informacyjnych. Projektowanie Baz Danych

Smarty PHP. Leksykon kieszonkowy

Ustawienie na poziomie sesji (działa do zmiany lub zakończenia sesji zamknięcia połączenia).

Projekt Hurtownia, realizacja skojarzeń dostawców i produktów

Sprawdzenie czy połączenie przebiegło poprawnie if (mysqli_connect_errno()) { echo Błąd; Połączenie z bazą danych nie powiodło się.

Podręcznik użytkownika produktu WebSphere Adapter for Oracle E-Business Suite Wersja 7, wydanie 0, pakiet składników Feature Pack 2

Tabela wewnętrzna - definicja

Imię i Nazwisko Data Ocena. Laboratorium 7

Programowanie w SQL procedury i funkcje. UWAGA: Proszę nie zapominać o prefiksowaniu nazw obiektów ciągiem [OLIMP\{nr indeksu}] Funkcje użytkownika

Wprowadzenie do projektowania i wykorzystania baz danych. Katarzyna Klessa

Microsoft.NET: LINQ to SQL, ASP.NET AJAX

Instytut Mechaniki i Inżynierii Obliczeniowej Wydział Mechaniczny Technologiczny Politechnika Śląska

Kwerenda. parametryczna, z polem wyliczeniowym, krzyżowa

Paweł Rajba

Pierwsza niedogodność dotyczy strony zarządzaj działami.

Budowa aplikacji ASP.NET współpracującej z bazą dany do przeprowadzania ankiet internetowych

Administracja i programowanie pod Microsoft SQL Server 2000

LK1: Wprowadzenie do MS Access Zakładanie bazy danych i tworzenie interfejsu użytkownika

Laboratorium 7 Blog: dodawanie i edycja wpisów

Instytut Mechaniki i Inżynierii Obliczeniowej Wydział Mechaniczny technologiczny Politechnika Śląska

Blaski i cienie wyzwalaczy w relacyjnych bazach danych. Mgr inż. Andrzej Ptasznik

Instytut Mechaniki i Inżynierii Obliczeniowej fb.com/groups/bazydanychmt/

Oracle PL/SQL. Paweł Rajba.

Projektowanie i programowanie aplikacji biznesowych. Wykład 2

6. Formularze tabelaryczne, obiekty nawigacji - rozgałęzienia

Budowa aplikacji ASP.NET współpracującej z bazą dany do przeprowadzania ankiet internetowych

D D L S Q L. Co to jest DDL SQL i jakie s jego ą podstawowe polecenia?

Wprowadzenie do projektowania i wykorzystania baz danych Relacje

akademia androida Składowanie danych część VI

SQL (ang. Structured Query Language)

Szablony klas, zastosowanie szablonów w programach

Budowa aplikacji ASP.NET współpracującej z bazą dany do obsługi przesyłania wiadomości

Projektowanie systemów baz danych

E.14 Bazy Danych cz. 18 SQL Funkcje, procedury składowane i wyzwalacze

Instytut Mechaniki i Inżynierii Obliczeniowej fb.com/groups/bazydanychmt/

Przewodnik Szybki start

Bazy danych Access KWERENDY

Wykład 9 Kolekcje, pliki tekstowe, Przykład: Notatnik

PRZESTRZENNE BAZY DANYCH WYKŁAD 2

Instytut Mechaniki i Inżynierii Obliczeniowej Wydział Mechaniczny technologiczny Politechnika Śląska

Programowanie MorphX Ax

TRX API opis funkcji interfejsu

System imed24 Instrukcja Moduł Analizy i raporty

Bazy danych. Zenon Gniazdowski WWSI, ITE Andrzej Ptasznik WWSI

PODSTAWOWE POJĘCIA BAZ DANYCH

Aktualizacja SMSFall v Data publikacji:

Nowe funkcje w programie Forte Finanse i Księgowość

Tworzenie projektu bazy danych z kreatorem odnośników - Filmoteka. Projekt tabel dla bazy Filmoteka

Joyce Cox Joan Lambert. Microsoft Access Krok po kroku. Przekład: Jakub Niedźwiedź

Wprowadzenie do Doctrine ORM

Cele. Definiowanie wyzwalaczy

Programowanie w języku Java. Bazy danych SQLite w Javie

Rozdział ten zawiera informacje o sposobie konfiguracji i działania Modułu OPC.

Systemy GIS Tworzenie zapytań w bazach danych

Generowanie dokumentów XML z tabel relacyjnych - funkcje SQLX

LABORATORIUM 8,9: BAZA DANYCH MS-ACCESS

1. Opis ogólny. 2. Opis techniczny. 3. Wymagania techniczne

Instrukcja obsługi aplikacji MobileRaks 1.0

Backend Administratora

Monitoring procesów z wykorzystaniem systemu ADONIS. Krok po kroku

Bazy Danych i Usługi Sieciowe

T-SQL dla każdego / Alison Balter. Gliwice, cop Spis treści. O autorce 11. Dedykacja 12. Podziękowania 12. Wstęp 15

DECLARE VARIABLE zmienna1 typ danych; BEGIN

Transkrypt:

Wykład 12 ADO.NET model bezpołączeniowy Klasa DataSet kolekcja DataTable Obiekty DataColumn Obiekty DataRow Ładowanie danych do obiektu DataSet użycie DataReader i DataAdapter Aktualizacja bazy za pomocą obiektu DataAdapter Definiowanie powiązań między tabelami w obiekcie DataSet 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. Tej klasy używa się 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 (naśladują 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 są tworzone 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

DataTable tabela = new DataTable("Zamowienie"); DataColumn kolumna = new DataColumn("ID", Type.GetType("System.Intl6")); kolumna.unique = true; //Wartość identyfikatora musi hyc // niepowtarzalna dla każdego wiersza danych kolumna.allowdbnull = false; tabela.columns.add(kolumna); kolumna = new DataColumn("Cena", Type.GetType("System.Decimal")); tabela.columns.add(kolumna); kolumna = new DataColumn("Ilosc", Type.GetType("System.Intl6")); tabela.columns.add(kolumna); kolumna = new DataColumn("Razem", Type.GetType("System.Decimal")); kolumna.expression = "Cena*Ilosc"; tabela.columns.add(kolumna); // Wyświetla nazwy kolumn i typ danych foreach (DataColumn dc in tabela.columns) { Console.WriteLine(dc.ColumnName); Console.writeLine(dc.DataType.ToString()); } 9

Obiekty DataRow Dane są dodawane do tabeli po utworzeniu nowego obiektu DataRow, zapełnianie go danymi z kolumn i dodawanie go do kolekcji Rows tabeli. Fragment kodu przedstawia przykład umieszczenia danych w tabeli utworzonej w poprzednim przykładzie: DataRow wiersz; wiersz = tb.newrow(); // Tworzenie obiektu DataRow wiersz["cena"] = 21.85; wiersz["ilosc"] = 2; wiersz["id"] = 12001; tabela.rows.add(wiersz); //Dodawanie wiersza do kolekcji Rows Console.WriteLine(tabela.Rows[0]["Razem"].ToString(); // 43,90 10

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. Rozszerzenie poprzedniego przykładu pokazuje, jak ustawiać te wartości: tabela.rows.add (wiersz); tabela.acceptchanges(); Console.Write(wiersz.RowState); tabela.rows[0].delete(); //Anulowanie usunięcia tabela.rejectchanges(); //Dodawanie //Zatwierdzanie zmian //Niezmieniony wpis // Usunięcie wpisu //Anulowanie zmian Console.Write(tabela.Rows[0].RowState); // Niezmieniony wpis DataRow myrow; myrow = tabela.newrow(): // Tworzenie obiektu 11

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() to 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: 12

DataRow r = tb.rows[0]; r["cena"] = 24.95; r.acceptchanges(); r["cena"] = 19.61; Console.WriteLine("Aktualna: {0} Oryginalna: {1} ", r["cena", DataRowVersion.Current], r["cena". DataRowVersion.Original]); //Dane wyjściowe: Aktualna: 19,61 Oryginalna: 24,95 13

Ładowanie danych do obiektu DataSet użycie obiektów DataReader i DataAdapter Obiektu DataReader można używać wraz z obiektami DataSet i DataTable do zapełniania tabeli wierszami danych wygenerowanymi przez zapytanie. Wymaga to utworzenia obiektu DataReader i przekazania go jako parametru do metody DataTable.Load(): cmd.commandtext = "SELECT * FROM studenci WHERE data_ur < 1980"; DbDataReader czytacz = cmd.executereader(commandbehavior.closeconnection); DataTable tabela = new DataTable("studenci"); tabela.load(czytacz); //Ładowanie danych i schematów do tabeli Console.WriteLine(czytacz.IsClosed); //True 14

Obiekt DataReader jest zamykany automatycznie po załadowaniu wszystkich wierszy. Parametr CloseConnection gwarantuje, że zamknięte zostanie także połączenie. Jeśli tabela już zawiera dane, metoda Load() scala nowe dane z istniejącymi wierszami. Scalanie zachodzi tylko wtedy, kiedy wiersze współdzielą klucz główny. Jeśli nie ma zdefiniowanego klucza głównego, nowe wiersze zostaną dodane na koniec tabeli. Przeciążona wersja metody Load() przyjmuje drugi parametr, który określa, w jaki sposób wiersze są scalane. Ten parametr to typ wyliczeniowy LoadOption, przyjmujący jedną z trzech wartości: OverwriteChanges, PreserveChanges lub Upsert. Te opcje określają, czy w wyniku scalania zostanie nadpisany cały wiersz, tylko oryginalne wartości czy tylko aktualne wartości. 15

Poniższy fragment kodu ilustruje, jak odbywa się scalanie danych z istniejącymi wierszami w celu nadpisania aktualnych wartości kolumn: cmd.commandtext = "SELECT * FROM studenci WHERE data_ur < 1980"; DbDataReader czytacz = cmd.executereader(); DataTable tabela = new DataTable("studenci"): tabela.load(czytacz); //Ładowanie wierszy do tabeli Console.Write(tabela.Rows[0][ num_stud"]); // Przypisanie klucza głównego, aby możliwe było scalenie // wierszy DataColumn[] kolumny = new DataColumn[1]; kolumny[0] = tabela.columns["num_stud"]; tabela.primarykey = kolumny; DataRow wiersz = tabela.rows[0]; //Pobranie pierwszego wiersza danych wiersz["num_stud"] = 99999; //Zmiana aktualnej wartości kolumny // Ponieważ obiekt DataReader jest zamknięty, trzeba go ponownie zapełnić czytacz = cmd.executereader(commandbehavior.closeconnection); //Scalanie danych z aktualnymi wierszami. Nadpisanie aktualnych wartości tabela.load(czytacz.loadoption.upsert); //Zaktualizowane wartości zostały nadpisane Console.Write(tabela.Rows[0]["num_stud"]); 16

Obiekt DataAdapter może służyć do zapełniania wynikami zapytania istniejącej tabeli lub do utworzenia nowej tabeli i zapełnienia jej tymi wynikami. Pierwszy etap tej operacji to utworzenie instancji obiektu DataAdapter dla konkretnego dostawcy danych. Do dyspozycji jest kilka przeciążonych wersji konstruktora: // (1) Najłatwiejszy sposób: przekazanie jako argumentów // zapytania i ciągu znaków połączenia String sql = "SELECT * FROM studenci"; SqlDataAdapter da = new SqlDataAdapter(sql, connstr); // (2) Przypisanie obiektu polecenia do właściwości // SelectCommand SqlDataAdapter da = new SqlDataAdapter(); SqlConnection conn = new SqlConnection(connStr); da.selectcommand = new SqlCommand(sql, conn); // (3) Przekazanie ciągu znaków zapytania i obiektu połączenia SqlConnection conn = new SqlConnection(connStr); SqlDataAdapter da = new SqlDataAdapter(sql, conn); 17

Konstruktor w pierwszej wersji przyjmuje dwa ciągi znaków polecenie i połączenie. Konstruktor tworzy na ich podstawie obiekt SqlCommand i przypisuje go do właściwości SelectCommand. Nie trzeba tu pisać kodu, który jawnie tworzy obiekty SqlCommand i SqlConnection. W wersjach przeciążonych, które przyjmują jako parametr obiekt połączenia, za otwieranie i zamykanie połączenia odpowiada obiekt DataAdapter. Jeśli doda się instrukcję jawnie otwierającą połączenie, trzeba także dodać kod, który je zamyka. W przeciwnym razie obiekt DataAdapter pozostawi połączenie otwarte, co blokuje dane w bazie danych. 18

Po utworzeniu obiektu DataAdapter wykonywana jest metoda Fill(), która ładuje dane do nowej lub istniejącej tabeli: DataSet ds = new DataSet(); // Tworzenie obiektu DataTable, ładowanie danych i //dodawanie ich do DataSet // Można użyć instrukcji da.fill(ds, "studenci"), aby // określić nazwę tabeli int ilerekordow = da.fill(ds); //Zwraca liczbę załadowanych rekordów W przypadku istniejących tabel działanie polecenia Fill zależy od tego, czy tabela ma klucz główny. Jeśli tak, wiersze mające klucz pasujący do klucza nowych danych zostają zastąpione. Nowe wiersze, które nie pasują do istniejących, są dodawane do obiektu DataTable. 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 zawierają implementację klasy CommandBuilder, której można użyć do automatycznej obsługi tych operacji. 21

Obiekt CommandBuider generuje polecenia niezbędne do aktualizacji źródła danych po wprowadzeniu zmian w obiekcie DataSet. Klasa CommandBuilder jest samowystarczalna. Można utworzyć jej obiekt, przekazując do konstruktora odpowiedni obiekt DataAdapter. Po wywołaniu metody DataAdapter.Update(), zostaną wygenerowane i wykonane polecenia 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); 22

// (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 (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. 23

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ć. 24

Jeśli właściwość DataAdapter.ContinueUpdateOnError ma wartość false, to wówczas gdy 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.net, 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. 25

SqlDataAdapter da = new SqlDataAdapter(); SqlCommandBuilder sb = new SqlCommandBuilder(da); SqlConnection conn = new SqlConnection(connStr); conn.open (); // Trzeba otworzyć połączenie, aby użyć go do transakcji //(1) Tworzenie transakcji SqlTransaction tran = conn.begintransaction(); // (2) Wiązanie polecenia z transakcją da.selectcommand = new SqlCommand(sql, conn, tran); DataSet ds = new DataSet(); da.fill(ds, "studenci"); //Kod w poniższym fragmencie aktualizuje wiersze w DataSet try { int updates = da.update(ds, "studenci"); MessageBox.Show("Aktualizacje: " + updates.tostringo); } 26

//(3) Jeśli zostanie zgłoszony wyjątek, wszystkie aktualizacje // w transakcji zostaną anulowane catch (Exception ex) { MessageBox.Show(ex.Message); //Błąd w czasie aktualizacji } if (tran!= null) { tran. Rollback(); //Anuluje wszystkie aktualizacje } finally { tran = null; MessageBox.Show("Wszystkie aktualizacje anulowano."); // (4) Jeśli nie było błędów, wszystkie aktualizacje zostają // zatwierdzone if (tran!= null) { tran.commit(); } MessageBox.Show("Wszystkie aktualizacje zakończyły się powodzeniem."); tran = null; } conn.close(); 27

Jeżeli właściwość DataAdapter.ContinueUpdateOnError ma wartość true, przetwarzanie nie zatrzymuje się, jeśli nie można zaktualizować wiersza. Wtedy obiekt DataAdapter aktualizuje wszystkie wiersze, które nie powodują błędów. W tej sytuacji programista odpowiada za identyfikację problematycznych wierszy i określa, jak poradzić sobie z ich przetwarzaniem. Wiersze, których aktualizacja się nie powiodła, można łatwo zidentyfikować, posługując się właściwością DataRowState. Wiersze, których aktualizacja się nie powiodła, mają wartość Unchanged. Niezaktualizowane wiersze mają oryginalne wartości sprzed próby modyfikacji: Added, Deleted lub Modified. 28

// Obiekt da typu SqlDataAdapter ładuje tabelę studenci da.continueupdateonerror = true; DataSet ds = new DataSet(); try { da.fill(ds, "studenci"); } DataTable dt = ds.tables["studenci"]; SglCommandBuilder sb = new SqlCommandBuilder(da); //Przykładowe aktualizacje dt.rows[29].delete() ; //Usunięcie dt.rows[30]["data_ur"] = "1983"; //Aktualizacja dt.rows[30]["nazwisko"] = "King Kong"; //Aktualizacja dt.rows[31]["nazwisko"] = "Fantasia"; //Aktualizacja DataRow drow = dt.newrow(); drow["nazwisko"] = "Mann"; drow["imie ] = "Thomas ; drow["data_ur"] = "1897-12-13"; Drow["plec"] = "M"; dt.rows.add(drow); //Wstawianie 29

//Przesłanie aktualizacji int updates = da.update(ds, studenci"): //Poniższe wyrażenie przyjmuje wartość true, jeśli któraś z //aktualizacji się nie powiodła if (ds.haschanged()) { // Ładuje problematyczne wiersze do obiektu DataSet DataSet failures = ds.getchanges(); int rowsfailed = failures.tables[0].rows.count; Console.WriteLine("Nieudane aktualizacje: " + rowsfailed); foreach (DataRow r in failures.tables[0].rows) { string state = r.rowstate.tostring(); // Trzeba odrzucić zmiany, aby wyświetlić usunięty wiersz if (r.rowstate == DataRowState.Deleted) { r.rejectchanges(); } string ID = ((int)r["num_stud"]).tostring(); string msg = state + " ID filmu: " + ID; Console.WriteLine(msg); } } 30

Definiowanie powiązań między tabelami w obiekcie DataSet DataRelation to relacja tabela nadrzędna - tabela podrzędna między dwoma obiektami DataTable. Definiuje się ją na podstawie pasujących kolumn w obu tabelach. Kolumny muszą mieć ten sam typ danych (DataType), a kolumna w tabeli nadrzędnej musi zawierać niepowtarzalne wartości. Składnia konstruktora obiektu DataRelation wygląda następująco: public DataRelation (string nazwarelacji, DataColumn kolumnanadrzedna, DataColumn kolumnapodrzedna) Klasa DataSet ma właściwość Relations, która daje dostęp do kolekcji obiektów DataRelation zdefiniowanych dla tabel obiektu DataSet. Do umieszczania relacji w kolekcji służy metoda Relations.Add(). Przykład pokazany dalej zawiera kod, który ustanawia relację nadrzędna-podrzędna między tabelami studenci i rejestr, co pozwala na wyświetlenie listy kursów, na które zapisał się każdy ze studentów. 31

DataSet ds = new DataSet(); // (1) Zapełnianie tabeli kursami string sql = "SELECT * FROM Rejestr"; SąlConnection conn = new SqlConnection(connStr); SqlCommand cmd = new SqlCommand(); SqlDataAdapter da = new SqlDataAdapter(sql, conn); da.fill(ds, "rejestr"); // (2) Zapełnianie tabeli studentami sql = "SELECT num_stud, nazwisko, imie FROM studenci"; da.selectcommand.commandtext = sql; da.fill(ds, "studenci"); // (3) Definiowanie relacji między tabelami DataTable parent = ds.tables["studenci"]; DataTable child = ds.tables["rejestr"]; 32

DataRelation relation = new DataRelation("studenci_rejestr", parent.columns["num_stud"], child.columns["num_stud"]); // (4) Dodanie relacji do obiektu DataSet ds.relations.add(relation); // (5) Wyświetla wszystkich studentów i ich kursy foreach (DataRow r in parent.rows) { Console.WriteLine(r["nazwisko"] + r["imie ]); // Nazwisko i Imię studenta { } foreach (DataColumn rc in Console.WriteLine(" " + rc["kurs"]); r.getchildrows("studenci_rejestr")) } 33

Kiedy między dwiema tabelami zdefiniowana jest relacja, powoduje to dodanie więzów ForeignKeyConstraint do kolekcji Constraints podrzędnej tabeli DataTable. Określają one, jak zmiana lub usunięcie wierszy w tabeli nadrzędnej wpływa na tabelę podrzędną. Oznacza to, że po usunięciu wiersza w tabeli nadrzędnej wiersz w tabeli podrzędnej także może zostać usunięty lub jego klucz może przyjąć wartość null. W podobny sposób, jeśli w tabeli nadrzędnej zmianie ulegnie wartość klucza, wartość klucza wiersza w tabeli podrzędnej może ulec takiej samej zmianie lub może przyjąć wartość null. 34

Do określania obowiązujących zasad służą wartości właściwości DeleteRule i UpdateRule więzów. Mogą one przyjmować jedną z czterech wartości wyliczenia Rule: Cascade usuwa lub aktualizuje wiersze w tabeli podrzędnej. Jest to ustawienie domyślne. None nie powoduje żadnych zmian. SetDefault ustawia wartości kluczy w wierszach podrzędnych na domyślne wartości kolumny. SetNull ustawia wartości kluczy w wierszach podrzędnych na null. 35

// (1) Próba dodania do tabeli podrzędnej wiersza z // nowym kluczem DataRow row = child.newrow(); row[ num_stud"] = 999; child.rows.add (row); // Próba nieudana //Niepowodzenie 999 nie istnieje w tabeli nadrzędnej // (2) Usuwanie wiersza w tabeli nadrzędnej row = parent.rows[0]; row. Delete (); //Powoduje usunięcie wierszy w tabeli // o tym samym kluczu w tabeli podrzędnej // (3) Zwolnienie więzów i ponowna próba dodania wiersza ds.enforceconstraints = false; row[ num_stud"] = 999; child.rows.add(row); //Próba kończy się powodzeniem ds.enforceconstraints = true; //Ponowne włączenie więzów // (4) Zmiana działania więzów powodująca wstawianie // null po zmianie w tabeli nadrzędnej ((ForeignKeyConstraint)child.Constraints[0]).DeleteRule = Rule.SetNull; 36

Model połączeniowym 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. 37

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. 38

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. 39