Rozdział 12.
Zastosowanie zaawansowanych technik obsługi danych
Bardzo szybko poznawałeś różne metody dostępu do danych. Teraz, kiedy już opanowałeś podstawy, nadszedł czas aby przejść na kolejny, bardziej zaawansowany poziom. W tym rozdziale skoncentrujemy się na technikach bardziej zaawansowanych w porównaniu z tymi, przedstawionymi w poprzednich częściach niniejszej książki. Poznasz w nim nowe bazy danych oraz nowe metody operowania na danych XML, które umożliwią Ci tworzenie profesjonalnych aplikacji internetowych.
W pierwszej kolejności przyjrzymy się metodom pobierania informacji przy użyciu parametrów i procedur zachowanych. Parametry pozwalają na tworzenie zapytań w sposób bardziej efektywny. Procedury zachowane są natomiast przygotowywanymi wcześniej poleceniami SQL, których wykorzystanie przynosi wiele korzyści, do których należy zaliczyć zwiększenie szybkości działania aplikacji oraz poprawienie jej przejrzystości. Dowiesz się także, w jaki sposób, poprzez wykorzystanie transakcji, można zapewnić, że polecenia SQL będą wykonywane poprawnie.
W drugiej części tego rozdziału znajdziesz dodatkowe informacje na temat języka XML. Poznasz klasę XmlNavigator, której można używać podobnie jak klasy XmlDocument. Wykorzystując obiekty tej klasy, można zadawać pytania XPath oraz wykonywać przekształcenia XSL. XPath to języka zapytań stosowany wraz z językiem XML, natomiast XSL to język pozwalający na przekształcanie plików XML do postaci wszelkich innych dokumentów strukturalnych, takich jak na przykład dokumenty HTML. Aby stać się ekspertem w dziedzinie ASP.NET koniecznie należy poznać wiele różnych sposobów umożliwiających uzyskanie tego samego rezultatu. Po przeczytaniu tego rozdziału będziesz już znał kilka różnych sposobów pobierania informacji z baz danych oraz plików XML.
W tym rozdziale omówione zostaną następujące zagadnienia:
Czym są zapytania parametryzowane oraz jak należy je stosować.
Czym są procedury zachowane oraz jak należy je stosować.
Jak wykorzystywać transakcje.
Jakie są inne sposoby odczytywania zawartości dokumentów XML.
Sposoby przeszukiwania plików XML.
Sposoby przekształcania plików XML.
Zaawansowane techniki obsługi baz danych
Jak na razie wszelkie operacje związane z wykorzystaniem baz danych były wykonywane przy użyciu obiektów OleDbDataAdapter, OleDbCommand, DataSet oraz kilku innych. Dostarczają one wszelkich możliwości funkcjonalnych potrzebnych przy tworzeniu aplikacji ASP.NET. Jednak nie poznałeś jeszcze wszystkich możliwości jakie obiekty te dają, w szczególności chodzi tu o parametry, procedury zachowane oraz transakcje.
Parametry są nowym sposobem tworzenia dynamicznych poleceń SQL. Otóż zamiast tworzyć pytanie SQL składając je z fragmentów pochodzących z wielu różnych źródeł, można wykorzystać parametry, które poinformują bazę danych jakie informacje należy zwrócić. Takie rozwiązanie jest nie tylko prostsze, lecz także bardziej eleganckie. Procedury zachowane są natomiast przygotowywanymi wcześniej poleceniami SQL, które mogą poprawić efektywność działania aplikacji. Wykorzystanie procedur zachowanych wraz z parametrami stanowi doskonały sposób przeszukiwania i pobierania informacji z baz danych. Transakcje pozwalają natomiast na zapewnienie integralności i poprawności informacji poprzez wykonywanie operacji na bazach danych zgodnie z paradygmatem „wszystko albo nic”. Oznacza to, że zostaną wprowadzone wszystkie modyfikacje lub nie zostanie wprowadzona żadna z nich. Poznasz także kilka najczęstszych sytuacji, w których są wykorzystywane transakcje.
Dzięki tym zaawansowanym technikom obsługi baz danych będziesz w stanie tworzyć aplikacje o znacznie większych możliwościach, a jednocześnie poprawić efektywność ich działania.
Zapytania sparametryzowane
Wyobraź sobie proces budowy domu. Konieczna jest przy tym szczegółowa znajomość wymiarów elementów konstrukcyjnych oraz miejsc, w których należy ich użyć. Jednym ze sposobów realizacji tego zagadnienia byłoby zebranie wszystkich elementów i zapisanie wszystkich informacji bezpośrednio na nich. Na wszystkich panelach podłogowych, oknach, ościeżnicach, rurach i wszelkich innych elementach, zostałyby zapisane wymiary oraz docelowe położenie. Taka metoda mogłaby spełnić swoje zadanie, lecz jednocześnie jest bardzo niedokładna i może stać się przyczyną wielu problemów. Zamiast niej, można jednak wykorzystać plan — kartkę papieru, która informuje gdzie mają być umieszczone poszczególne elementy i jak je należy połączyć.
Podobnie rzecz się ma z dostępem do danych. Gdy tworzone jest polecenie SQL, można zebrać poszczególne jego elementy i połączyć je ze sobą w jeden łańcuch znaków, zapisując jednocześnie informacje o tym za co odpowiadają jego poszczególne części. Można jednak wykorzystać bardziej zorganizowaną metodę określenia, gdzie należy umieścić poszczególne informacje. Ta druga metoda wykorzystuje parametry — fragmenty informacji tworzone niezależnie od polecenia SQL i używane w nim. Bazy danych posługują się parametrami w taki sam sposób, w jaki budowniczy korzystają z planów.
Parametry wykorzystywane są w raz z obiektami OleDbCommand w celu podania dodatkowych informacji, takich jak dane, które należy zwrócić lub sposób ich zapisania w obiekcie DataSet. Przypomnij sobie informacje dotyczące poleceń SQL podane w rozdziale 10, pt.: „Korzystanie z baz danych za pomocą obiektów ADO.NET”. Aby dynamicznie stworzyć zapytanie, niejednokrotnie trzeba pobierać informacje z różnych elementów kontrolnych wykorzystywanych na stronach ASP.NET. Przykładowo, wyobraź sobie następujące zapytanie:
strSQL = "select * from tblUsers where UserID = 1"
Wartość UserID mogłaby pochodzić z pola tekstowego wyświetlonego na stronie. A zatem, można by stworzyć zapytanie w następujący sposób (zakładając, że tbId jest nazwą pola tekstowego):
strSQL = "select * from tblUsers where UserID = " & tbId.Text
Powyższa metoda spełnia swoje zadanie jeśli chodzi o zapewnienie możliwości dynamicznego stworzenia zapytania SQL, jednak nie jest „zorganizowana”. Co by się bowiem stało gdyby na stronie były inne pola tekstowe? Wtedy mogłyby się pojawić problemy z określeniem, które z pól powinno zawierać jakie informacje; największe problemy mieliby inni programiści próbujący przeanalizować kod strony.
Nowe określenie
Jednak bardziej efektywną metodą jest użycie parametrów. Parametr jest wartością przekazywaną do, bądź zwracaną przez zapytanie. Wykorzystanie parametrów pozwala na zachowanie przejrzystości informacji i ułatwia analizę stosowanych zapytań. Zastąpmy zatem poprzednie zapytanie, zapytaniem sparametryzowanym:
strSQL = "select * from tblUsers where UserID = @ID"
Jak widać dynamicznie tworzona część łańcucha znaków została zastąpiona parametrem zapytania, oznaczonym przy użyciu symbolu @. Zauważ, iż parametr ten stanowi część zapytania SQL. Teraz, w jakimś miejscu, należy podać wartość tego parametru. Wartości parametrów określane są przy użyciu kolekcji Parameters obiektu OleDbCommand (więcej informacji na jego temat znajdziesz w rozdziale 10). Przykład wykorzystania sparametryzowanych zapytań SQL przedstawiłem na listingu 12.1.
Listing 12.1. Określanie wartości parametrów zapytania SQL — fragment kodu
dim objCmd as OleDbCommand = new OleDbCommand _
("select * from tblUsers where UserID = @ID", Conn)
dim objParam as OleDbParameter
objParam = objCmd.Parameters.Add("@ID", OleDbType.Integer)
objParam.Direction = ParameterDirection.Input
objParam.Value = tbId.Text
Analiza
W wierszach 1. oraz 2., w normalny sposób jest tworzony obiekt OleDbCommand (zakładam przy tym, że został już utworzony obiekt OleDbConnection o nazwie Conn). Warto zwrócić uwagę na wiersz 2., w którym tworzone jest zapytanie sparametryzowane. W wierszu 4. tworzony jest obiekt OleDbCommand, który zostanie wykorzystany do przekazania wartości parametru do zapytania SQL. W wierszu 5. parametr użyty w zapytaniu jest dodawany do obiektu OleDbCommand, a jednocześnie określana jest jego nazwa i typ. Nazwa parametry podana w wywołaniu metody Add musi odpowiadać parametrowi użytemu w zapytaniu SQL (w tym przypadku musi ona mieć postać @ID). Typ jest wartością OleDbType i reprezentuje typ przekazywanej wartości parametru. Najczęściej stosowane wartości typów przedstawione zostały w tabeli 12.1.
Tabela 12.1. Najczęściej stosowane wartości OleDbType
Typ |
Opis |
Binary |
Strumień bajtów (odpowiada tablicy bajtów) |
Boolean |
Wartość logiczna |
BSTR |
Łańcuch znaków (odpowiada wartości typu String) |
Char |
Łańcuch znaków (odpowiada wartości typu String) |
Currency |
Wartość monetarna (odpowiada wartości typu Decimal) |
Date |
Data (odpowiada wartości typu DateTime) |
Decimal |
Wartość typu Decimal |
Double |
Wartość typu Double |
Empty |
Brak wartości |
Error |
32-bitowy kod błędu (odpowiada wartości typu Exception) |
Integer |
32-bitowa liczba całkowita (odpowiada wartości typu Integer) |
LongVarChar |
Długi łańcuch znaków (odpowiada wartości typu String) |
VarChar |
Łańcuch znaków (odpowiada wartości typu String) |
Variant |
Specjalny typ danych, który może reprezentować dane dowolnego typu, jeśli żaden typ nie został określony (odpowiada wartości typu Object) |
W wierszu 6. określany jest rodzaj parametru. W tym przypadku parametr będzie wykorzystywany jako element zapytania SELECT, a zatem jego wartość będzie przekazywana do zapytania. Stąd też, właściwości określającej rodzaj zostanie przypisana wartość Input. Gdyby wartość była zwracana i zapisywana w parametrze, to jego rodzaj należałoby określić jako Output. Więcej informacji na temat kierunków przekazywania informacji przez parametry podam w dalszej części rozdziału, poświęconej procedurom zachowanym.
W końcu, w wierszu 7., określana jest wartość parametru; w tym przypadku zostaje jej przypisana zawartość pola tekstowego tbId. Dowolne informacje podane w tym polu tekstowym zostaną zatem przekazane jako parametr do zapytania select. Przeanalizuj teraz pełny kod przykładu, podany na listingu 12.2.
Listing 12.2. Wykorzystanie parametrów do pobierania informacji z baz danych
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<script runat="server">
dim Conn as new OleDbConnection("Provider=" & _
"Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\ASPNET\Data\banking.mdb")
sub GetData(obj as Object, e as EventArgs)
dim objCmd as OleDbCommand = new OleDbCommand _
("select * from tblUsers where UserID = @ID", Conn)
dim objReader as OleDbDataReader
dim objParam as OleDbParameter
objParam = objCmd.Parameters.Add("@ID", _
OleDbType.Integer)
objParam.Direction = ParameterDirection.Input
objParam.Value = tbId.Text
try
objCmd.Connection.Open()
objReader = objCmd.ExecuteReader
catch ex as OleDbException
Label1.Text = "Błąd pobierania informacji z bazy danych."
end try
DataGrid1.DataSource = objReader
DataGrid1.DataBind()
objReader.Close
objCmd.Connection.Close()
end sub
</script>
<html><body>
<form runat="server">
<asp:Label id="Label1" runat="server" /><br>
Podaj ID: <asp:TextBox id="tbID" runat="server"
AutoPostBack=True
OnTextChanged=GetData /><p>
<asp:DataGrid id="DataGrid1" runat="server"
BorderColor="black" GridLines="Vertical"
cellpadding="4" cellspacing="0" width="100%"
Font-Name="Arial" Font-Size="8pt"
HeaderStyle-BackColor="#cccc99"
ItemStyle-BackColor="#ffffff"
AlternatingItemStyle-Backcolor="#cccccc"
AutoGenerateColumns="true" />
</form>
</body></html>
Analiza
Kod przedstawiony na powyższym listingu powinien wyglądać znajomo — jest on bowiem bardzo podobny do przykładów wykorzystania informacji pobieranych z baz danych, przedstawionych w rozdziale 10. W części przykładu zawierającej kod HTML stworzone zostały trzy elementy sterujące obsługiwane na serwerze: DataGrid (zdefiniowany w wierszach od 42. do 49.), Label (zdefiniowany w wierszu 39.) oraz TextBox (zdefiniowany w wierszach od 39. do 41.). Gdy użytkownik wpisze jakąś wartość w polu tekstowym, zostanie zgłoszone zdarzenie TextChanged; w wyniku jego obsługi, w elemencie sterującym DataGrid zostaną wyświetlone odpowiednio przefiltrowane informacje (zwróć uwagę na atrybut AutoPostBack=True umieszczony w wierszu 40.). W wierszu 6. deklarowany jest obiekt OleDbConnection, który będzie wykorzystywany w procedurze GetData.
Procedura GetData jest wykonywana w celu obsługi zdarzenia TextChanged generowanego przez pole tekstowe i służy do wyświetlenia odpowiednich informacji. Nasze sparametryzowane zapytanie jest tworzone w wierszach 11. i 12. W wierszach do 16. do 19. tworzony jest parametr, określany kierunek przekazywania informacji oraz jego wartość (wyznaczana na podstawie wartości właściwości Text pola tekstowego tbID). Pozostała część procedury pobiera dane i wiąże je z elementem sterującym DataGrid. Na rysunku 12.1 przedstawione zostały wyniki wygenerowane przez powyższy przykład, po wpisaniu jakiejś wartości w polu tekstowym.
Rysunek 12.1. Sparametryzowane zapytanie wykorzystujące zawartość pola tekstowego w celu określenia informacji jakie należy wyświetlić
W jednym zapytaniu można użyć wielu różnych parametrów. Na przykład:
strSQL = "SELECT * FROM tblUsers WHERE UserID=@ID AND FirstName=@Name"
W parametrze można także umieścić wartość zwracaną przez zapytanie:
strSQL = "SELECT @Phone=Phone FROM tblUsers WHERE UserID=@ID
AND FirstName=@Name"
Parametry zwracające informacje określane są jako parametry wyjściowe. W efekcie wykonania powyższego zapytania (SELECT Phone FROM tblUsers WHERE UserID=@ID AND FirstName=@Name) zwrócona przez nie wartość zostanie zapisana w parametrze @Phone. Po wykonaniu tego zapytania, wartość parametru będzie można pobrać z kolekcji parametrów. Przedstawiony poniżej fragment kodu tworzy parametr wyjściowy dla ostatniego, przedstawionego wcześniej zapytania SQL:
dim objParam as OleDbParameter
objParam = objCmd.Parameters.Add("@Phone", OleDbType.BSTR)
objParam.Direction = ParameterDirection.Output
Wartość tego parametru można pobrać po wykonaniu zapytania, przy wykorzystaniu właściwości Value:
dim strPhone as string = objParam.Value
Parametry są niezwykle przydatne przy tworzeniu dynamicznych zapytań, jednak ich prawdziwe możliwości uwidaczniają się dopiero przy zastosowaniu wraz z procedurami zachowanymi.
Procedury zachowane
Procedura zachowana jest zbiorem poleceń (zazwyczaj poleceń SQL połączonych z instrukcjami zapisanymi w innym języku, charakterystycznym dla używanej bazy danych), które baza danych jest w stanie wykonać. Jaka jest różnica pomiędzy procedurą zachowaną a zwyczajnymi poleceniami SQL?
Po pierwsze procedury zachowane są kompilowane. Już wiesz jakie korzyści daje kompilowanie stron ASP.NET. Bardzo podobne korzyści daje kompilacja procedur zachowanych; dotyczy to także zwiększenia szybkości ich działania.
Nowe określenie
Gdy baza danych wykonuje procedurę zachowaną, tworzony jest plan wykonania, który pozwala na szybsze pobranie informacji w przypadku ponownego wykonania tej samej procedury. Baza danych analizuje dane oraz zapytanie i określa najbardziej efektywny sposób pobrania i zwrócenia informacji. Sposób ten zapisywany jest następnie w planie wykonania. A zatem, korzyści jakie daje stosowanie procedur zachowanych nie wynikają wyłącznie z faktu iż są one kompilowane — dodatkową zaletą jest stworzenie i późniejsze wykorzystania planu wykonania.
Wykorzystanie procedur zachowanych pozwala na stworzenie kolejnego poziomu abstrakcji pomiędzy stronami ASP.NET a danymi. Należy pamiętać, że modularność jest jednym z celów programowania obiektowego. Dzięki oddzieleniu zapytań SQL od stron ASP.NET można:
pozwolić na wielokrotne stosowanie tych samych zapytań,
ułatwić analizę kodu strony ASP.NET,
zaoszczędzić czas.
Jeśli wykorzystywane polecenie SQL jest bardzo długie, to umieszczenie go w stronie ASP.NET powoduje dodanie do niej kodu, który wcale nie jest tam potrzebny. Takie zapytanie jedynie utrudni analizę kodu obsługującego faktyczne możliwości funkcjonalne strony. Co więcej, ze względu na fakt, iż kod zapytania musi zostać przesłany ze strony ASP.NET na serwer bazy danych, taki sposób wykonywania zapytań powoduje także niepotrzebne wykorzystanie przepustowości łączy. I w końcu, wyobraź sobie, że to samo zapytanie SQL jest wykorzystywane w kilku stronach ASP.NET. Jeśli struktura bazy danych ulegnie jakimkolwiek zmianom i pojawi się konieczność zmiany zapytania, to niezbędne poprawki trzeba będzie wprowadzić na każdej ze stron, na których dane zapytanie jest używane. W przypadku wykorzystania procedur zachowanych zmianę wystarczy wprowadzić w jednym miejscu, a jej skutki obejmą całą aplikację.
Przeniesienie poleceń SQL do procedur zachowanych eliminuje wszystkie powyższe problemy, a jednocześnie daje kilka innych korzyści. Implementacja nawet bardzo prostych, jednowierszowych zapytań SQL w formie procedur zachowanych da duże korzyści.
Lecz zakończmy te rozważania teoretyczne i stwórzmy w końcu jakąś procedurę zachowaną!
Tworzenie procedur zachowanych w SQL Serverze 2000
Procedury zachowane są wykorzystywane w wielu różnych systemach baz danych. Na przykład, wykorzystują je zarówno SQL Server jak Microsoft Access, choć w każdej z tych baz danych są one tworzone w odmienny sposób. W tej części rozdziału pokażę jak należy tworzyć procedury zachowane w Microsoft SQL Serverze 2000. W następnej części rozdziału zajmiemy się Accessem.
Właśnie z tego powodu, w tej części rozdziału użyjemy SQL Servera, pomimo tego, iż w pozostałych częściach książki wykorzystywany był Microsoft Access. Spróbujmy zatem stworzyć prostą procedurę zachowaną.
Otwórz Enterprise Managera, tak samo jak robiliśmy to w rozdziale 8., pt.: „Podstawowe wiadomości na temat tworzenia baz danych”. Rozwiń węzły Microsoft SQL Server, SQL Server Group, jak również węzeł z nazwą serwera bazy danych oraz węzeł Databases. Następnie otwórz bazę danych Banking stworzoną w rozdziale 8. W tym celu kliknij symbol „+” wyświetlony przy węźle Banking (patrz rysunek 12.2).
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Rysunek 12.2. Wyświetl zawartość bazy danych Banking stworzonej w rozdziale 8
SQL Server został wyposażony w dużo wbudowanych procedur zachowanych. Można je przejrzeć klikając węzeł Stored Procedures, a następnie dwukrotnie klikając jedną z nazw wyświetlonych w prawej części okna. Większość z tych predefiniowanych procedur będzie znacznie bardziej skomplikowana do procedury którą teraz stworzymy; jednak analizując je będziesz mógł zobaczyć jak powinny wyglądać Twoje procedury. Kliknij prawym przyciskiem myszy na ikonie procedur zachowanych i z menu podręcznego wybierz opcje New Stored Procedure. Na ekranie pojawi się nowe okienko dialogowe, przypominające to pokazane na rysunku 12.3.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Rysunek 12.3. W tym oknie dialogowym można podać polecenia SQL, które będą tworzyć procedurę zachowaną
Wyrażenie [OWNER].[PROCEDURE NAME] zastąp nazwą jaką chcesz nadać tworzonej procedurze, na przykład: SelectIdFromName. (W tym przypadku nie musisz zwracać uwagi na atrybut OWNER. Więcej informacji na jego temat znajdziesz w dokumentacji SQL Servera 2000.) Ponieważ chcemy stworzyć sparametryzowane zapytanie, a zatem bezpośrednio po nazwie procedury i przed słowem kluczowym AS należy zdefiniować używane parametry:
CREATE PROCEDURE SelectIdFromName
@FirstName varchar,
@LastName varchar,
@ID int OUTPUT
AS
Słowo kluczowe OUTPUT informuje, że w danym parametrze należy zapisać wartość i zwrócić ją do programu, który wywołał procedurę. Po słowie kluczowym AS można podać treść zapytania SQL:
SELECT @ID = UserID
FROM tblUsers
WHERE FirstName = @FirstName
AND LastName = @LastName
Powyższe zapytanie pobiera wartość pola UserID dla użytkownika o podanym imieniu i nazwisku (polach FirstName i LastName) i zapisuje ją w parametrze @ID. Później wykorzystamy go jako parametr wyjściowy w kodzie strony ASP.NET. Ewentualnie można także kliknąć przycisk Check syntax, aby sprawdzić czy kod procedury zachowanej został poprawnie zapisany. SQL Server sprawdzi kod procedury i poinformuje Cię jeśli znajdzie w nim jakiekolwiek błędy. Aby zapisać procedurę, kliknij przycisk OK. Teraz nazwa naszej procedury zachowanej powinna się pojawić na liście, wraz z nazwami procedur predefiniowanych.
Tworzenie procedur zachowanych w Accessie 2000
Ponieważ we wcześniejszej części książki używaliśmy Microsoft Accessa, zatem pokażę jak można tworzyć procedury zachowane także w tej aplikacji. Access umożliwia tworzenie procedur zachowanych jednak określa je jako „kwerendy”. Otwórz zatem bazę danych Banking stworzoną w rozdziale 8 i kliknij zakładkę Kwerendy wyświetloną z lewej strony okna pokazanego na rysunku 12.4.
Rysunek 12.4. W Accessie procedury zachowane są określane jako „kwerendy”
Znasz zasady tworzenia poleceń SQL, a zatem nie musisz korzystać z kreatorów. Dwukrotnie kliknij opcję Utwórz kwerendę w widoku projektu. Na ekranie pojawi się okienko dialogowe o nazwie Pokazywanie tabeli, zamknij je klikając przycisk Zamknij. Spójrz na pasek narzędzi Accessa, z jego lewej strony powinieneś zauważyć rozwijaną listę o nazwie Widok. Wyświetl jej zawartość, a następnie wybierz z niej opcję Widok SQL, tak jak pokazałem na rysunku 12.5.
Rysunek 12.5. Przejdź do trybu edycji kodu zapytania SQL klikając rozwijaną listę Widok wyświetloną z lewej strony paska narzędzi Accessa
Teraz możesz pisać kod zapytania SQL bezpośrednio w tworzonej kwerendzie:
SELECT UserID FROM tblUsers
WHERE FirstName = @FirstName
AND LastName = @LastName
Wpisz kod zapytania w oknie kwerendy, jak pokazałem na rysunku 12.6, a następnie zamknij okno klikając przycisk X widoczny w jego prawym górnym wierzchołku i kliknij przycisk Tak, aby zapisać kwerendę. Zapisz kwerendę pod nazwą SelectIDFromName. Teraz powinieneś ją zobaczyć na liście wszystkich dostępnych kwerend.
Rysunek 12.6. Wprowadzanie zapytań w programie Microsoft Access, w widoku SQL
Notatka
Mechanizm obsługi baz danych Accessa nie daje możliwości stosowania parametrów wyjściowych w kwerendach. Właśnie z tego względu w powyższej kwerendzie nie znajdziesz parametru @ID. W dalszej części rozdziału dowiesz się jak można ominąć to ograniczenie.
Użycie procedur zachowanych w stronach ASP.NET
Wykonanie procedury zachowanej z poziomu strony ASP.NET jest proste. W tym celu należy określić wartość jednej właściwości, której do tej pory nie używaliśmy. Właściwość ta nosi nazwę CommandType:
dim objCmd as OleDbCommand = new OleDbCommand _
("SelectIDFromName", Conn)
objCmd.CommandType = CommandType.StoredProcedure
W pierwszym wierszu powyższego fragmentu kodu, w standardowy sposób jest tworzony obiekt OleDbCommand. Jednak tworząc go nie podaliśmy kodu polecenia SQL, lecz nazwę utworzonej przed chwilą procedury zachowanej. W 3. wierszu, przypisując właściwości CommandType wartość StoredProcedure, informujemy ASP.NET, że zostanie wykorzystana procedura zachowana. Po uzyskaniu tej informacji ADO.NET odszuka procedurę zachowaną w bazie danych, wykona ją i zwróci uzyskane informacje wynikowe.
Jednak takie wykonanie naszej przykładowej procedury zachowanej nie dałoby oczekiwanych rezultatów. Przypomnij sobie, że procedura ta zawierała parametry. Aby zwróciła ona jakiekolwiek dane, konieczne będzie określenie wartości parametrów @FirstName oraz @LastName. Można to zrobić na dwa sposoby — bezpośrednio lub przy wykorzystaniu kolekcji OleDbParameters. Pierwszy z tych sposobów jest bardzo prosty. Wystarczy zmienić pierwszy wiersz powyższego fragmentu kodu, w sposób przedstawiony na kolejnym przykładzie (przy czym wartości są wartościami parametrów jakie należy przekazać):
dim objCmd as OleDbCommand = new OleDbCommand _
("SelectIDFromName wartosc, wartosc", Conn)
Na przykład:
dim objCmd as OleDbCommand = new OleDbCommand _
("SelectIDFromName 'Chris', 'Payne'", Conn)
Metoda ta jest bardzo łatwa, lecz niezbyt efektywna, zwłaszcza jeśli jako parametry wykorzystywane są wartości pobierane, na przykład, z elementów sterujących formularzy. W przypadku wykorzystania kolekcji parametrów, wartości parametrów naszej przykładowej procedury zachowanej można by określić w następujący sposób:
dim objParam as OleDbParameter
objParam = objCmd.Parameters.Add("@FirstName", OleDbType.Char)
objParam.Direction = ParameterDirection.Input
objParam.Value = tbFirst.Text
objParam = objCmd.Parameters.Add("@LastName", OleDbType.Char)
objParam.Direction = ParameterDirection.Input
objParam.Value = tbLast.Text
Ten kod powinien Ci coś przypominać. Dokładnie te same czynności wykonywałeś wcześniej w tym rozdziale, w części pod tytułem „Zapytania sparametryzowane”. Teraz możesz w standardowy sposób wypełnić obiekt DataReader (wywołując metodę ExecuteReader obiektu polecenia) i związać dane z elementem sterującym wyświetlanym na stronie:
try
objCmd.Connection.Open()
objReader = objCmd.ExecuteReader
catch ex as OleDbException
Label1.Text = "Błąd pobierania informacji z bazy danych."
end try
DataGrid1.DataSource = objReader
DataGrid1.DataBind()
objReader.Close
objCmd.Connection.Close()
A co się stało z naszym parametrem wyjściowym? W Accessie nie da się pobrać wartości @ID jako parametru wyjściowego. Niemniej jednak obiekt polecenia zwrócił wartość jako wynik wykonania zapytania SELECT. W poprzednim fragmencie kodu można uzyskać dostęp do tej wartości za pośrednictwem obiektu DataReader. Na listingu 12.3 został przedstawiony kompletny przykład utworzony poprzez połączenie wszystkich wcześniejszych fragmentów kodu, wyniki jego pokazano na rysunku 12.7.
Listing 12.3. Parametry ułatwiają pobieranie danych
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<script runat="server">
dim Conn as new OleDbConnection("Provider=" & _
"Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\ASPNET\Data\banking.mdb")
sub SubmitData(obj as Object, e as EventArgs)
dim objCmd as OleDbCommand = new OleDbCommand _
("SelectIDFromName", Conn)
dim objReader as OleDbDataReader
objCmd.CommandType = CommandType.StoredProcedure
dim objParam as OleDbParameter
objParam = objCmd.Parameters.Add("@FirstName", _
OleDbType.Char)
objParam.Direction = ParameterDirection.Input
objParam.Value = tbFirst.Text
objParam = objCmd.Parameters.Add("@LastName", _
OleDbType.Char)
objParam.Direction = ParameterDirection.Input
objParam.Value = tbLast.Text
try
objCmd.Connection.Open()
objReader = objCmd.ExecuteReader
catch ex as OleDbException
Response.Write("Błąd pobierania informacji z bazy danych.")
end try
DataGrid1.DataSource = objReader
DataGrid1.DataBind()
objReader.Close
objCmd.Connection.Close()
end sub
</script>
<html><body>
<form runat="server">
Podaj imię:
<asp:TextBox id="tbFirst" runat="server" /><br>
Podaj nazwisko:
<asp:TextBox id="tbLast" runat="server" /><p>
<asp:Button id="btSubmit" runat="server"
text="Wyślij"
OnClick="SubmitData"/><p>
<asp:DataGrid id="DataGrid1" runat="server"
BorderColor="black"
GridLines="Vertical"
cellpadding="4"
cellspacing="0"
width="100%"
Font-Name="Arial"
Font-Size="8pt"
HeaderStyle-BackColor="#cccc99"
ItemStyle-BackColor="#ffffff"
AlternatingItemStyle-Backcolor="#cccccc"
AutoGenerateColumns="true" />
</form>
</body></html>
Rysunek 12.7. Zwracanie wartości przez sparametryzowane procedury zachowane
Obiekt DataReader zawiera jeden wiersz oraz jedną kolumnę, w której znajduje się wartość zwrócona przez procedurę zachowaną.
Notatka
W przypadku SQL Servera 2000 stosowanie parametrów wyjściowych jest ze wszech miar zalecane; a zatem można się nimi posłużyć w powyższym przykładzie i zrezygnować z wykorzystania obiektu DataReader. Więcej informacji na ten temat znajdziesz we wcześniejszej części rozdziału, pt.: „Zapytania sparametryzowane”.
Parametry wejściowe i wyjściowe są niezwykle przydatne przy przekazywaniu danych do i z procedur zachowanych. Istnieją jednak jeszcze inne typy parametrów — takie jak InputOutput lub ReturnValue — których jeszcze nie poznałeś. Wszystkie dostępne rodzaje parametrów zostały przedstawione w tabeli 12.2.
Tabela 12.2. Rodzaje parametrów (kierunki przekazywania informacji)
Rodzaj |
Opis |
Input |
Reprezentuje wartość przekazywaną do zapytania. |
InputOutput |
Wartość która może być zarówno przekazana do zapytania jak i zwrócona przez nie. |
Output |
Wartość zwracana w wyniku wykonania zapytania. |
ReturnValue |
Reprezentuje wartość zwracaną przez zapytanie, która jednak nie jest parametrem. |
Przedstawiłem już sposoby wykorzystania parametrów wejściowych (Input) oraz wyjściowych (Output). Parametry InputOutput są przydatne w sytuacjach, gdy dane przekazane do zapytania mogą ulec zmianie (na przykład, w przypadku aktualizacji bazy). Prosty przykład takiego zapytania przedstawiłem na poniższym przykładzie, w którym wartość pola FirstName należy zmodyfikować w zależności od jego wartości:
UPDATE tblUsers SET FirstName = "Christopher"
WHERE FirstName = "Chris"
Zapytanie to można by sparametryzować w następujący sposób:
UPDATE tblUsers SET @FirstName = "Christopher"
WHERE @FirstName = "Chris"
W tym przypadku @FirstName jest zarówno parametrem wejściowym jak i wyjściowym, a po wykonaniu polecenia SQL jego wartość ulegnie zmianie. Parametry InputOutput doskonale nadają się właśnie do takich sytuacji.
Parametry ReturnValue są bardzo przydatne w poleceniach SQL, które nie zwracają żadnych wartości pobranych z kolumn bazy danych. Na przykład, przedstawione poniżej zapytanie zwraca liczbę całkowitą określającą ilość wierszy tabeli:
SELECT Count(*) FROM tblUsers
Zwracana informacja nie pochodzi z żadnej konkretnej kolumny, a zatem nie jest z nią skojarzona żadna nazwa pola. Wartość ta jest zwracana przez zapytanie bez żadnej nazwy ani parametru. Dane tego typu wspaniale nadają się do obsługi przy użyciu parametrów ReturnValue. Aby pobrać wartość zwracaną przez powyższe zapytanie, należy posłużyć się następującym fragmentem kodu:
objParam = objCmd.Parameters.Add("RETURN VALUE", OleDbType.Integer)
objParam.Direction = ParameterDirection.ReturnValue
Procedury zachowane są niezwykle przydatnym narzędziem, które może poprawić efektywność działania aplikacji ASP.NET. Udostępniają one nowe możliwości interakcji z bazami danych, pozwalając na stosowanie bardzo złożonych zapytań i użycie bardziej zaawansowanych elementów sterujących baz danych.
Transakcje
Ile razy wykonywałeś skomplikowane zadanie i w połowie zdawałeś sobie sprawę z tego, że wszystko jest zrobione źle? Czy nie marzyłeś o tym, aby cofnąć czas i zacząć wszystko od nowa?
Bazy danych są w stanie spełnić to marzenie, a wszystko dzięki transakcjom. Transakcja to zbiór pewnych czynności, z których wszystkie muszą zostać wykonane poprawnie lub nie zostanie wykonana żadna z nich. Na przykład, wyobraź sobie że stworzyłeś złożoną procedurę zachowaną składającą się z 50 poleceń SQL. Jeśli by nie było transakcji, to gdyby któreś z tych poleceń — dajmy na to 50-te — zostało wykonane nieprawidłowo, to realizacja całej procedury zostałaby przerwana i jedno z poleceń nigdy nie byłoby wykonane. Gdybyś chciał wykonać to polecenie, to musiałbyś najpierw ponownie wykonać 49 poleceń poprzedzających je.
Istnieje bardzo wiele przypadków, gdy takie przerwanie wykonywania serii poleceń jest wysoce niepożądane. Rozważmy przykład aplikacji bankowej. Użytkownik chce przelać pewną kwotę pieniędzy ze swego konta rozliczeniowego, na konto oszczędnościowe. W pierwszej kolejności należy zatem odjąć podaną kwotę z konta rozliczeniowego (którego stan jest przechowywany w bazie danych), a następnie dodać ją do konta oszczędnościowego (którego stan także jest przechowywany w bazie danych). Załóżmy, że pierwszy etap operacji został wykonany poprawnie. Jednak podczas próby dodania przelewanej sumy na konto oszczędnościowe okazuje się, iż zostało ono zablokowane i w danej chwili nie można do niego niczego dodać. O rany, no to mamy problem. Kwoty zapisane w bazie danych są nieprawidłowe gdyż z kąta rozliczeniowego pieniądze już zostały odjęte.
Transakcje zostały zaprojektowane z myślą o właśnie takich sytuacjach. Jeśli w powyższej procedurze zostałaby użyta transakcja, to nie musielibyśmy się przejmować jakimikolwiek problemami jakie mogłyby się wydarzyć w trakcie wykonywania całej operacji. Jeśli cokolwiek by się stało, można by bez problemów odtworzyć wykonane czynności.
Być może przypominasz sobie metody AcceptChanges oraz RejectChanges klasy DataSet, o których wspominałem w rozdziale 10. Pozwalają one na wykonywanie czynności przypominających transakcje, lecz działają wyłącznie na informacjach „odłączonych” (czyli już pobranych z bazy danych). Transakcje obejmują swym działaniem całą bazę danych wykorzystując przy tym aktywne połączenie, zakładając oczywiście, że serwer bazy danych w ogóle jest w stanie obsługiwać transakcje (większość komercyjnych serwerów baz danych dysponuje tą możliwością).
Trzema podstawowymi operacjami każdej z transakcji są — rozpoczęcie transakcji, jej anulacja bądź zatwierdzenie. Transakcja zaczyna się w momencie jej rozpoczęcia. Wszystkie kolejne czynności są wykonywane i zapisywane w specjalnym dzienniku, dzięki czemu baza danych może je później przejrzeć. Anulacja transakcji powoduje odtworzenie wszelkich modyfikacji jakie zostały wprowadzone. Baza danych odwołuje się przy tym do dziennika i na jego podstawie jest w stanie określić jaką postać miały informacje w momencie rozpoczynania transakcji. Zatwierdzenie transakcji sprawia, że informacje zostają uznane za ostateczne i nie będzie ich już można odtworzyć. W konsekwencji, zatwierdzenie oznacza usunięcie z dziennika bazy danych informacji o danej transakcji.
Przyjrzymy się teraz typowemu przykładowi wykorzystania transakcji, przedstawionemu na listingu 12.4.
Listing 12.4. Zastosowanie transakcji
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<script runat="server">
'deklarujemy polaczenie'
dim Conn as new OleDbConnection("Provider=" & _
"Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\ASPNET\Data\banking.mdb")
sub Page_Load(obj as Object, e as EventArgs)
dim objTrans as OleDbTransaction
dim objCmd as OleDbCommand = new OleDbCommand _
("DELET FROM tblUsers WHERE UserID=32", Conn)
Conn.Open()
objTrans = Conn.BeginTransaction()
objCmd.Transaction = objTrans
try
objCmd.ExecuteNonQuery
objCmd.CommandText = "INSERT INTO tblUsers " & _
"(FirstName, LastName, Address, City, State, " & _
"Zip, Phone) VALUES " & _
"('Jose', 'Santiago', '34 Lake Drive', " & _
"'Yolktown', 'MA', '02515', '8006579876')"
objCmd.ExecuteNonQuery()
objTrans.Commit()
Label1.Text = "Obie operacje zostały wykonane poprawnie."
catch ex as OleDbException
objTrans.RollBack()
Label1.Text = ex.Message & "<p>" & _
"Żadna operacja nie została wykonana."
finally
objCmd.Connection.Close()
end try
end sub
</script>
<html><body>
<form runat="server">
<asp:Label id="Label1" runat="server"
maintainstate=false/>
</form>
</body></html>
Analiza
Powyższa strona ASP.NET wykonuje dwa polecenie SQL. Jednak jeśli podczas wykonywania któregoś z nich pojawi się nieprzewidziany problem, to w bazie danych nie zostaną wprowadzone żadne modyfikacje. Na przykład, jeśli drugie polecenie byłoby zapisane w nieodpowiedni sposób, to nasza operacja zostałaby przerwana w połowie. Jednak dzięki temu, iż przed wykonaniem jakichkolwiek czynności rozpoczynamy transakcję (w wierszu 17.), to możemy odtworzyć wszelkie modyfikacje wprowadzone w bazie w wyniku wykonania pierwszego polecenia.
W wierszu 7., w standardowy sposób, jest tworzony obiekt OleDbConnection. W wierszu 12. jest tworzony obiekt OleDbTransaction, a w wierszu 14. określamy pierwsze polecenie SQL jakie zostanie wykonane. Rozpoczęcie transakcji wymaga otworzonego połączenia z bazą danych, a zatem, w wierszu 16., otwierane jest połączenie. W wierszu 17. rozpoczynamy transakcję, wywołując w tym celu metodę BeginTransaction.
Należy zwrócić uwagę iż BeginTransaction jest metodą klasy OleDbConnection. Transakcja musi bowiem zostać zainicjalizowana przez obiekt połączenia. Ten sposób rozpoczynania transakcji został opracowany celowo, aby nie można było stosować transakcji w razie korzystania z serwerów baz danych, które ich nie obsługują. Obiekt połączenia jest w stanie określić czy baza danych obsługuje transakcje czy nie i uniemożliwić rozpoczęcie transakcji jeśli zajdzie taka potrzeba. Wszystkie dalsze polecenia (anulowanie transakcji lub jej zatwierdzenie) są wykonywane przy użyciu obiektu OleDbTransaction.
W wierszu 18. wskazujemy obiektowi OleDbCommand, który obiekt OleDbTransaction będzie wykorzystywany. Do tego celu służy właściwość Transaction. Wewnątrz bloku try wykonujemy polecenie SQL DELET, a następnie tworzymy i próbujemy wykonać polecenie SQL INSERT. Jeśli wszystko pójdzie zgodnie z planem, to będzie można wywołać metodę Commit, aby zaakceptować modyfikacje wprowadzone w bazie danych.
Jeśli jednak coś pójdzie nie tak jak zaplanowaliśmy, to chcemy, aby wszelkie modyfikacje zostały odtworzone. Blok try przechwytuje wyjątek i przekazuje wykonywanie do instrukcji catch, gdzie wywoływana jest metoda RollBack i wyświetlany stosowny komunikat. Niezależnie od tego czy został zgłoszony wyjątek czy nie, wykonywany jest blok finally, w którym zamykamy połączenie z bazą danych.
W rezultacie wykorzystania transakcji zostaną wykonane wszystkie polecenia SQL bądź nie zostanie wykonane żadne z nich.
Zaawansowane techniki obsługi danych XML
W poprzednim rozdziale dowiedziałeś się w jaki sposób można wykorzystać język XML do przedstawienia niemal każdego typu informacji. Poznałeś także metody otwierania, odczytywania oraz zapisywania dokumentów XML z poziomu stron ASP.NET. Niemniej jednak nie są to wszystkie możliwości wykorzystania i obsługi danych XML.
W kolejnych częściach tego rozdziału przedstawionych zostanie kilka bardziej zaawansowanych technik manipulowania danymi XML. Dowiesz się jak można się poruszać po dokumentach XML przy wykorzystaniu obiektów klasy XmlNavigator, który pozwala na wykorzystanie dwóch technologii przedstawionych w dalszej części rozdziału — zapytań XPath oraz przekształceń XSL. XPath jest językiem zapytań stosowanych do pobierania informacji z dokumentów XML (podobnie jak język SQL służy do pobierania informacji z baz danych). Przekształcenia XSL pozwalają na zapisanie zawartości dokumentu XML w formie dokumentu strukturalnego dowolnego innego typu, na przykład — strony HTML. Dzięki tym technologiom uzyskasz pełną kontrolę nad dokumentami XML i ich zawartością.
XPathDocument
Nowe określenie
W poprzednim rozdziale dowiedziałeś się jak można poruszać się po tekstowych dokumentach XML przy wykorzystaniu obiektów klas XmlNode i XmlDocument. W przypadku prób uzyskania dostępu do danych XML, obiekty tych dwóch klas tworzą drzewo węzłów. Oznacza to, że odczytują one zawartość całego pliku XML i tworzą obiektową, hierarchiczną reprezentację zapisanych w nim informacji. Najprościej rzecz biorąc, obiekt XmlDocument pobiera całą zawartość pliku XML zanim będzie można uzyskać do niej dostęp. Jednak obiekty klasy XPathDocument nie tworzą drzew węzłów. Zamiast tego analizują zawartość dokumentu po jednym węźle. Obiekty te tworzą obiektową reprezentację węzła gdy zostanie on odczytany. Jeśli chcesz, możesz je sobie wyobrazić jako dynamiczne obiekty XmlDocument.
Klasa XPathDocument przypomina nieco klasę XmlDocument, lecz została stworzona z myślą o zapewnieniu jak największej efektywności działania i z tego względu nie dysponuje równie bogatymi możliwościami. Klasa ta została zoptymalizowana pod kątem wykonywania zapytań XPath (stąd też pochodzi jej nazwa — XPathDocument) i przekształceń XSL. Oba te zagadnienia — zapytania XPath oraz przekształcenia XSL — zostaną opisane w dalszej części rozdziału. Najpierw jednak przyjrzyjmy się sposobowi wykorzystania obiektu XPathDocument przedstawionemu na listingu 12.5.
Listing 12.5. Tworzenie dokumentu XPathDocument
<%@Page Language="VB" %>
<%@Import Namespace="System.Xml" %>
<%@Import Namespace="System.Xml.XPath" %>
<script runat="server">
sub Page_Load(obj as object,e as eventargs)
'Tworzymy obiekt XPathDocument'
Dim objDocument as New XPathDocument _
(Server.MapPath("../rozdzial11/books.xml"))
end sub
</script>
<html><body>
</body></html>
Analiza
W rzeczywistości kod przedstawiony na powyższym listingu nie zawiera niczego nowego — w wierszach 8. i 9. jest jedynie tworzony nowy obiekt XPathDocument. W rzeczywistości, to jest wszystko co można zrobić z obiektem klasy XPathDocument, nie można go bowiem użyć ani do poruszania się po zawartości pliku XML ani do jego edycji. Obiekty te zostały stworzone w celu zapewnienia szybkiego dostępu do zawartości plików XML i przesyłania jej bezpośrednio do innych obiektów, które ją przetworzą.
Konkretnie rzecz biorąc, z obiektem XPathDocument najczęściej będzie wykorzystywany obiekt XPathNavigator. Dostarcza on metod służących do poruszania się po zawartości plików XML. W jego skład wchodzą wyłącznie te metody, które sprawiają, iż jest on efektywnym narzędziem nawigacyjnym. Przykład wykorzystania obiektów XPathDocument oraz XPathNavigator przedstawiłem na listingu 12.6.
Listing 12.6. Poruszanie się po dokumencie XML przy wykorzystaniu obiektu XPathNavigator
<%@Page Language="VB" %>
<%@Import Namespace="System.Xml" %>
<%@Import Namespace="System.Xml.XPath" %>
<script runat="server">
sub Page_Load(obj as object,e as eventargs)
Dim objDocument as New XPathDocument _
(Server.MapPath("../rozdzial11/books.xml"))
Dim objNav as XPathNavigator = objDocument. _
CreateNavigator
objNav.MoveToRoot()
DisplayTree(objNav)
end sub
public sub DisplayTree (objNav as XPathNavigator )
if (objNav.HasChildren)
objNav.MoveToFirstChild()
Format(objNav)
DisplayTree(objNav)
objNav.MoveToParent()
end if
while (objNav.MoveToNext())
Format (objNav)
DisplayTree (objNav)
end while
end sub
private sub Format (objNav as XPathNavigator)
if Not objNav.HasChildren
if (objNav.NodeType = XPathNodeType.Text)
lblMessage.Text += "" & objNav.Value & "<br>"
end if
else
lblMessage.Text += "<" & objNav.Name & _
"><br>"
if objNav.HasAttributes
while (objNav.MoveToNextAttribute())
lblMessage.Text += " <" & _
objNav.Name & "> " & objNav.Value & _
"<br>"
end while
objNav.MoveToParent()
end if
end if
end sub
</script>
<html><body>
<ASP:Label id="lblMessage" runat="server"/>
</body></html>
Znaczna część powyższego kodu przypomina przykłady przedstawione w poprzednim rozdziale, wykorzystujące obiekty XmlDocument oraz XmlNode. W wierszach 7. i 8. tworzony jest obiekt XPathDocument, dokładnie w taki sam sposób jak na przykładzie przedstawionym na listingu 12.5. W wierszach 10. i 11. wywoływana jest metoda CreateNavigator obiektu XPathDocument. Metoda ta tworzy obiekt XPathNavigator, którego będziemy używali do poruszania się po dokumencie XML. Metoda MoveToRoot wywoływana w wierszu 12. powoduje przejście na sam początek pliku XML, i w końcu w wierszu 13. wywoływana jest stworzona przez nas procedura DisplayTree.
Procedura DisplayTree zdefiniowana w wierszach od 16. do 30, jest procedurą rekurencyjną. W pierwszej kolejności określa ona czy aktualnie analizowany węzeł ma jakiekolwiek węzły podrzędne. Jeśli ma, to będziemy chcieli wyświetlić informacje o każdym z nich. Wywołanie metody MoveToFirstChild przesuwa kursor do pierwszego węzła podrzędnego. Za samo wyświetlenie informacji o węźle na wynikowej stronie WWW odpowiada procedura Format, którą niebawem zostanie opisana. W wierszu 21. ponownie wywoływana jest procedura DisplayTree, która powtórzy cały proces dla aktualnie przetwarzanego węzła podrzędnego. Powyższy proces jest powtarzany dla wszystkich węzłów podrzędnych. Gdy każdy z nich zostanie już przetworzony, zostaje wywołana metoda MoveToParent, która przesunie kursor o jeden poziom w górę hierarchii dokumentu XML.
Także druga część procedury jest rekurencyjna. Odpowiada ona za przeanalizowanie węzłów znajdujących się na tym samym poziomie hierarchii dokumentu. Do kolejnego węzła na tym samym poziomie hierarchii można przejść przy wykorzystaniu metody MoveNext. Metoda ta jest wywoływana cyklicznie, aż do momentu gdy przetworzone zostaną wszystkie węzły; w tym przypadku metoda zwróci wartość false, a wykonywania pętli while zostanie zakończone. Wewnątrz pętli wywoływane są metody Format oraz DisplayTree. Proces ten jest wykonywany aż do chwili, gdy wszystkie węzły dokumentu XML zostaną przetworzone.
Teraz przyjrzyjmy się procedurze Format, zdefiniowanej w wierszach od 32. do 51. Mam nadzieję, że procedura ta Cię nie przeraża — przeważająca jej część po prostu generuje formatujące znaczniki HTML. Instrukcja if umieszczona w wierszu 33. określa czy dany węzeł ma jakiekolwiek węzły podrzędne. Jeśli nie ma, to procedura wyświetli jedynie wartość aktualnie przetwarzanego węzła. Jeśli jednak bieżący węzeł będzie miał jakieś węzły potomne, to wyświetlona zostanie jego wartość zapisana w nawiasach kątowych. Kolejna instrukcja if, zapisana w wierszu 41., określa czy przetwarzany węzeł ma jakiekolwiek atrybuty i wyświetla odpowiedni komunikat, jeśli jakieś atrybut zostaną odnalezione. I w końcu, pętla while rozpoczynająca się w wierszu 42. pobiera po kolei wszystkie atrybuty węzła i wyświetla je. Wyniki wykonania powyższego przykładu przedstawiłem na rysunku 12.8.
Rysunek 12.8. Wykorzystanie obiektu XPathNavigator do wyświetlania danych XML
XPath
XPath jest specyfikacją języka służącego do pobierania fragmentów plików XML, opracowaną przez Konsorcjum World Wide Web (w skrócie: W3C). Język ten pozwala na zadawanie pytań służących do przeszukiwania zawartości plików XML, podobnie jak zapytania SQL służą do przeszukiwania informacji przechowywanych w bazach danych. Język XPath może być dosyć złożony, więc w tym rozdziale nawet nie spróbuję opisywać jego składni. Zamiast tego skoncentruję się na zademonstrowaniu sposobu wykorzystania zapytań XPath do przeszukiwania dokumentów XML.
Zapytania XPath są łańcuchami znaków składającymi się ze słów kluczowych reprezentujących fragmenty plików XML. Zapytania te są wykonywane przez metodę Select klasy XPathNavigator. Zakładają, że chcielibyśmy korzystać z naszego przykładowego pliku books.xml, wywołanie tej metody mogłoby przyjąć poniższą, przykładową postać:
objNav.Select("descendant::book/author/last-name")
Powyższe zapytanie zwróci nazwiska wszystkich autorów wszystkich książek opisanych w pliku. Drugie przykładowe zapytanie XPath zwróci natomiast wyłącznie cenę ostatniej książki:
objNav.Select("//book[last()]/price/text()")
Na listingu 12.7 został przedstawiony przykład prostej strony ASP.NET, która umożliwia podanie zapytania XPath, a następnie je wykonuje i wyświetla uzyskane wyniki.
Listing 12.7. Wykorzystanie zapytań XPath do pobierania danych XML
<%@Page Language="VB" %>
<%@Import Namespace="System.Xml" %>
<%@Import Namespace="System.Xml.XPath" %>
<script runat="server">
sub SelectData(obj as object,e as eventargs)
Dim objDocument as New XPathDocument _
(Server.MapPath("../rozdzial11/books.xml"))
Dim objNav as XPathNavigator = objDocument.CreateNavigator
lblMessage.Text = ""
try
dim objIterator as XPathNodeIterator = _
objNav.Select(tbQuery.Text)
While objIterator.MoveNext()
lblMessage.Text += "<" & _
objIterator.Current.Name & "> " & _
objIterator.Current.Value & "<br>"
end while
catch ex As Exception
lblMessage.Text = ex.Message
end try
end sub
</script>
<html><body>
<form runat="server">
<h2>Zapytania XPath</h2>
<p>
Na przyład:<br>
<b><code>//book[last()]/@ISBN/text()</b></code> lub
<b><code>descendant::book/author/last-name</b></code><p>
Podaj zapytanie XPath:
<asp:Textbox id="tbQuery" runat=server/>
<asp:Button id="btnSubmit" text="Wykonaj zapytanie"
runat=server OnClick="SelectData"/><p>
<asp:Label id="lblMessage" runat=server/>
</form>
</body></html>
Analiza
W powyższym przykładzie, wszystkie najważniejsze czynności wykonywane są w procedurze SelectData, obsługującej zdarzenia generowane przez element sterujący przycisku zdefiniowany w wierszach 38. i 39. Kod zapisany w wierszach od 7. do 10. powinien wyglądać znajomo — odpowiada on za stworzenie obiektów XPathDocument oraz XPathNavigator. Prawdziwa zabawa zaczyna się natomiast w wierszu 13. gdzie rozpoczyna się blok try. Aby zwrócić poszukiwane dane XML, wykorzystywana jest metoda Select, w której wywołaniu zostaje podane zapytanie XPath wpisane przez użytkownika w polu tekstowym. Metoda ta zwraca obiekt XmlPathNodeIterator, który pozwala na łatwe przetworzenie uzyskanych wyników. Zwrócone wyniki są przetwarzane w pętli while, która, dzięki wykorzystaniu metody MoveNext, pobiera kolejno każdy ze zwróconych węzłów. Dla każdego z nich wyświetlana jest nazwa i wartość, przy czym informacje te są określane za pomocą właściwości Current.Name oraz Current.Value. Przykładowe wyniki wykonania powyższego przykładu, przedstawione zostały na rysunku 12.9.
Rysunek 12.9. Przeszukiwanie danych XML przy wykorzystaniu zapytań XPath
Język XPath stanowi bardzo potężny i doskonały mechanizm służący do pobierania danych XML. Dzięki niemu, nie będziesz już musiał wykonywać zapytań posługując się obiektami DataSet. Więcej informacji na temat zapytań XPath znajdziesz na witrynie WWW W3C, pod adresem http://www.w3.org/TR/xpath.
Przekształcenia XSL
Wszystkie instrukcje XSL są obsługiwane przez procesor przekształceń XSL (określany skrótowo jako XslT, od angielskich słów: XSL transform procesor). Język XSL służy do tworzenia arkuszy stylów, które informują XslT o tym, w jaki sposób należy przekształcić dane XML. Arkusze stylów XSL określają jak XslT ma sformatować dane XML po ich przetworzeniu; podobnie jak kaskadowe arkusze stylów informują przeglądarkę w jaki sposób ma sformatować poszczególne elementy strony WWW. Na rysunku 12.10 przedstawiłem proces przekształcania jednego dokumentu XML na drugi.
Rysunek 12.10. Procesor XSL wykorzystuje arkusze stylów XSL, aby przekształcić dokument XML na inny dokument strukturalny
Opis rysunku
XML file — Plik XML
XSL Stylesheet — Arkusz stylów XSL
XSL Procesor — Procesor przekształceń XSL
HTML document — Dokument HTML
XML document — Dokument XML
Other … — Inny dokument strukturalny
Procesor przekształceń XSL pobiera fragmenty pliku XML wykorzystując w tym celu zapytania XPath, a następnie formatuje je na podstawie arkusza stylów XSL. W ASP.NET przekształcenie dokumentu XML jest bardzo proste — trzeba tylko podać arkusz stylów XSL. Na listingu 12.8 przedstawiłem arkusz stylów books.xsl.
Listing 12.8. Arkusz stylów XSL
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="bookstore">
<HTML><BODY>
<TABLE width="450">
<TR>
<TD><b>Title</b></TD>
<TD><b>Price</b></TD>
</TR>
<xsl:apply-templates select="book"/>
</TABLE>
</BODY></HTML>
</xsl:template>
<xsl:template match="book">
<TR>
<TD><xsl:value-of select="title"/></TD>
<TD><xsl:value-of select="price"/></TD>
</TR>
</xsl:template>
</xsl:stylesheet>
Analiza
Powyższy arkusz stylów przekształca dokument XML na dokument HTML. Znaczniki xsl:template określają w jaki sposób należy przekształcać konkretne fragmenty dokumentu XML. Na przykład, fragment arkusza stylów rozpoczynający się w wierszu 20., określa postać wszystkich węzłów o nazwie book.
W tym przypadku przekształcamy dokument XML do postaci dokumentu HTML, co tłumaczy obecność znaczników HTML, BODY, itd. Jednak równie łatwo można by przekształcić go do postaci innego dokumentu XML — wszystko sprowadza się jedynie do podania odpowiednich znaczników.
Na listingu 12.9 przedstawiony został przykład strony ASP.NET, która wykorzystuje arkusz stylów XSL z listingu 12.8, aby przekształcić plik books.xml do postaci dokumentu HTML.
Listing 12.9. Wykorzystanie arkusza stylów XSL oraz procesora przekształceń XSL do przekształcenia pliku XML na dokument HTML
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Xml.XPath" %>
<%@ Import Namespace="System.Xml.Xsl" %>
<script runat="server">
sub Page_Load(obj as object, e as eventargs)
Dim objDocument as New XPathDocument _
(Server.MapPath("../rozdzial11/books.xml"))
Dim objNav as XPathNavigator = _
objDocument.CreateNavigator
Dim objXslT As XslTransform = New XslTransform()
dim objWriter as XmlTextWriter = new XmlTextWriter _
(Server.MapPath("output.html"), nothing)
try
objXslT.Load(Server.MapPath("books.xsl"))
objXslT.Transform(objNav, nothing, objWriter)
objWriter.Close
lblMessage.Text = "Plik został poprawnie zapisany."
catch ex As Exception
lblMessage.Text = ex.Message
end try
end sub
</script>
<html><body>
<asp:Label id="lblMessage" runat="server"
maintainstate=false/>
</body></html>
Analiza
Pierwszą rzeczą na jaką należy zwrócić uwagę jest dodatkowa przestrzeń nazw — System.Xml.Xsl — importowana w wierszu 4. W wierszach od 6. do 11. wykonywane są standardowe czynności — stworzenie obiektów XPathDocument oraz XPathNavigator. W wierszu 13. tworzony jest obiekt XslTransform, a w wierszu 14. obiekt XmlTextWriter, który posłuży nam do zapisania przekształconego dokumentu XML w pliku o nazwie output.html.
Wewnątrz bloku try plik XSL jest odczytywany i zapisywany w obiekcie XslTransform, który wykorzysta arkusz stylów jako schemat na podstawie którego zostanie określona postać nowego dokumentu HTML. Następnie wywoływana jest metoda Transform obiektu XslTransform, która przekształci dokument XML zgodnie z arkuszem stylów XSL. Pierwszym argumentem wywołania tej metody jest zawartość pliku XML, którą należy przekształcić (przekazana jako obiekt XPathNavigator, drugim — dodatkowe parametry jakie należy przekazać do pliku XSL (w tym przypadku żadne parametry nie są przekazywane), a trzecim — obiekt XmlTextWriter, w którym należy zapisać przekształcone dane XML.
W końcu, w wierszu 21. zamykany jest obiekt pisarza, a w wierszu 23. wyświetlany krótki komunikat informujący o przekształceniu pliku. Po wykonaniu powyższego przykładu, w tym samym folderze powinien się pojawić plik output.html. Będzie on zawierać informacje pobrane z oryginalnego pliku XML, przedstawione na rysunku 12.11.
Rysunek 12.11. Wyniki wykonania przekształcenia XSL zapisane w formie dokumentu HTML
Przekształcone dane XML można by także zapisać w obiekcie XmlReader i wyświetlić na stronie. W tym celu zmienić kod zapisany w wierszu 19. w następujący sposób:
objReader = objXslT.Transform(objNav, nothing)
W tym przypadku zmienna objReader jest obiektem XmlReader. I to wszystko. Listing 12.10 przedstawia pełny kod przykładu wykorzystującego obiekt XmlReader zamiast obiektu XmlTextWriter.
Listing 12.10. Wyświetlanie przekształconych danych XML przy wykorzystaniu obiektu XmlReader
<%@ Page Language="VB" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Xml.XPath" %>
<%@ Import Namespace="System.Xml.Xsl" %>
<script runat="server">
sub Page_Load(obj as object, e as eventargs)
Dim objDocument as New XPathDocument _
(Server.MapPath("../rozdzial11/books.xml"))
Dim objNav as XPathNavigator = _
objDocument.CreateNavigator
Dim objXSLT As XslTransform = New XslTransform()
dim objReader as XmlReader
try
objXSLT.Load(Server.MapPath("books.xsl"))
objReader = objXslT.Transform(objNav, nothing)
While objReader.Read()
Response.Write("<b>" & objReader.Name & "</b> " & _
objReader.Value & "<br>")
End While
lblMessage.Text = "Plik został poprawnie zapisany."
catch ex As Exception
lblMessage.Text = ex.Message
end try
end sub
</script>
<html><body>
<asp:Label id="lblMessage" runat="server"
maintainstate=false/>
</body></html>
Powyższy przykład różni się od kodu z listingu 12.9 jedynie tym, iż został w nim wykorzystany obiekt XmlReader a nie XmlTextWriter. Z tego względu HTML uzyskany w wyniku przekształcenia nie jest zapisywany w pliku. W wierszach od 19. do 22. znajduje się pętla while, która przy użyciu metody Read pobiera po kolei wszystkie elementy danych wynikowych i wyświetla je.
Obiekt XmlTransform dysponuje jedynie dwiema metodami — Load oraz Transform — a zatem jego wykorzystanie nie powinno przysparzać większych problemów.
Pamiętasz zapewne, że procesor przekształceń XSL wykorzystuje zapytania XPath. Plik XSL określa nazwy węzłów, które należy przekształcić. Obiekt XmlTransform wykorzystuje zapytania XPath do pobrania węzłów o określonych nazwach, przy czym Ty jako programista nawet nie wiesz w jaki sposób cały ten proces jest realizowany. Równie dobrze można by samemu użyć tych zapytań o pobrania danych, a następnie je sformatować. Jednak po co się męczyć jeśli obiekty XmlTransform mogą zrobić to za nas?
Więcej informacji na temat XSL można znaleźć na witrynie W3C, na stronach: http://www.w3.org/TR/xsl oraz http://www.w3.org/TR/xslt.
To nie jest ASP!
Wiele spośród technik opisanych w tym rozdziale jest także dostępnych we wcześniejszej, tradycyjnej wersji technologii ASP. Na przykład, sparametryzowane procedury zachowane były kiedyś niezwykle popularnym sposobem wykonywania zapytań SQL. Także operacje na danych XML można było wykonywać, choć był do tego potrzebny specjalny, dodatkowy komponent ASP. ASP.NET udostępnia jednak znacznie prostsze sposoby wykonywania tych wszystkich czynności, gdyż wszystkie konieczne możliwości funkcjonalne są wbudowane bezpośrednio w środowisko .NET i są w pełni obiektowe.
Wielu programistów ASP spotkało się już z omawianymi tu zagadnieniami, a zatem wykorzystanie ich w środowisku .NET nie powinno przysparzać większych trudności. Zmianie uległ jedynie sposób implementacji; wciąż można natomiast korzystać z istniejących procedur zachowanych, arkuszy stylów i zapytań XPath. Jedyne co będziesz musiał zrobić, to korzystać z nich przy użyciu innych obiektów.
Rodzaj parametru nazywany jest także „kierunkiem”, gdyż określa on kierunek w jakim parametr będzie przekazywał informacje.
2 Część I ♦ Podstawy obsługi systemu WhizBang (Nagłówek strony)
2 Dokument2