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
1
dim objCmd as OleDbCommand = new OleDbCommand _
2
("select * from tblUsers where UserID = @ID", Conn)
3
4
dim objParam as OleDbParameter
5
objParam = objCmd.Parameters.Add("@ID", OleDbType.Integer)
6
objParam.Direction = ParameterDirection.Input
7
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
1
. 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
1
<%@ Page Language="VB" %>
2
<%@ Import Namespace="System.Data" %>
3
<%@ Import Namespace="System.Data.OleDb" %>
4
5
<script runat="server">
6
dim Conn as new OleDbConnection("Provider=" & _
7
"Microsoft.Jet.OLEDB.4.0;" & _
8
"Data Source=C:\ASPNET\Data\banking.mdb")
9
10
sub GetData(obj as Object, e as EventArgs)
11
dim objCmd as OleDbCommand = new OleDbCommand _
12
("select * from tblUsers where UserID = @ID", Conn)
13
dim objReader as OleDbDataReader
14
dim objParam as OleDbParameter
15
16
objParam = objCmd.Parameters.Add("@ID", _
17
OleDbType.Integer)
18
objParam.Direction = ParameterDirection.Input
19
objParam.Value = tbId.Text
20
21
try
22
objCmd.Connection.Open()
23
objReader = objCmd.ExecuteReader
24
catch ex as OleDbException
25
Label1.Text = "Bł
ą
d pobierania informacji z bazy danych."
26
end try
27
28
DataGrid1.DataSource = objReader
29
DataGrid1.DataBind()
30
31
objReader.Close
32
objCmd.Connection.Close()
33
end sub
34
</script>
35
36
<html><body>
37
<form runat="server">
38
<asp:Label id="Label1" runat="server" /><br>
39
Podaj ID: <asp:TextBox id="tbID" runat="server"
40
AutoPostBack=True
41
OnTextChanged=GetData /><p>
42
<asp:DataGrid id="DataGrid1" runat="server"
43
BorderColor="black" GridLines="Vertical"
44
cellpadding="4" cellspacing="0" width="100%"
45
Font-Name="Arial" Font-Size="8pt"
46
HeaderStyle-BackColor="#cccc99"
1
Rodzaj parametru nazywany jest takŜe „kierunkiem”, gdyŜ określa on kierunek w jakim parametr będzie
przekazywał informacje.
47
ItemStyle-BackColor="#ffffff"
48
AlternatingItemStyle-Backcolor="#cccccc"
49
AutoGenerateColumns="true" />
50
</form>
51
</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).
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.
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
1
<%@ Page Language="VB" %>
2
<%@ Import Namespace="System.Data" %>
3
<%@ Import Namespace="System.Data.OleDb" %>
4
5
<script runat="server">
6
dim Conn as new OleDbConnection("Provider=" & _
7
"Microsoft.Jet.OLEDB.4.0;" & _
8
"Data Source=C:\ASPNET\Data\banking.mdb")
9
10
sub SubmitData(obj as Object, e as EventArgs)
11
dim objCmd as OleDbCommand = new OleDbCommand _
12
("SelectIDFromName", Conn)
13
dim objReader as OleDbDataReader
14
objCmd.CommandType = CommandType.StoredProcedure
15
16
dim objParam as OleDbParameter
17
objParam = objCmd.Parameters.Add("@FirstName", _
18
OleDbType.Char)
19
objParam.Direction = ParameterDirection.Input
20
objParam.Value = tbFirst.Text
21
22
objParam = objCmd.Parameters.Add("@LastName", _
23
OleDbType.Char)
24
objParam.Direction = ParameterDirection.Input
25
objParam.Value = tbLast.Text
26
27
try
28
objCmd.Connection.Open()
29
objReader = objCmd.ExecuteReader
30
catch ex as OleDbException
31
Response.Write("Bł
ą
d pobierania informacji z bazy danych.")
32
end try
33
34
DataGrid1.DataSource = objReader
35
DataGrid1.DataBind()
36
37
objReader.Close
38
objCmd.Connection.Close()
39
end sub
40
</script>
41
42
<html><body>
43
<form runat="server">
44
Podaj imi
ę
:
45
<asp:TextBox id="tbFirst" runat="server" /><br>
46
Podaj nazwisko:
47
<asp:TextBox id="tbLast" runat="server" /><p>
48
49
<asp:Button id="btSubmit" runat="server"
50
text="Wy
ś
lij"
51
OnClick="SubmitData"/><p>
52
53
<asp:DataGrid id="DataGrid1" runat="server"
54
BorderColor="black"
55
GridLines="Vertical"
56
cellpadding="4"
57
cellspacing="0"
58
width="100%"
59
Font-Name="Arial"
60
Font-Size="8pt"
61
HeaderStyle-BackColor="#cccc99"
62
ItemStyle-BackColor="#ffffff"
63
AlternatingItemStyle-Backcolor="#cccccc"
64
AutoGenerateColumns="true" />
65
</form>
66
</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
1
<%@ Page Language="VB" %>
2
<%@ Import Namespace="System.Data" %>
3
<%@ Import Namespace="System.Data.OleDb" %>
4
5
<script runat="server">
6
'deklarujemy polaczenie'
7
dim Conn as new OleDbConnection("Provider=" & _
8
"Microsoft.Jet.OLEDB.4.0;" & _
9
"Data Source=C:\ASPNET\Data\banking.mdb")
10
11
sub Page_Load(obj as Object, e as EventArgs)
12
dim objTrans as OleDbTransaction
13
dim objCmd as OleDbCommand = new OleDbCommand _
14
("DELET FROM tblUsers WHERE UserID=32", Conn)
15
16
Conn.Open()
17
objTrans = Conn.BeginTransaction()
18
objCmd.Transaction = objTrans
19
20
try
21
objCmd.ExecuteNonQuery
22
23
objCmd.CommandText = "INSERT INTO tblUsers " & _
24
"(FirstName, LastName, Address, City, State, " & _
25
"Zip, Phone) VALUES " & _
26
"('Jose', 'Santiago', '34 Lake Drive', " & _
27
"'Yolktown', 'MA', '02515', '8006579876')"
28
objCmd.ExecuteNonQuery()
29
objTrans.Commit()
30
Label1.Text = "Obie operacje zostały wykonane poprawnie."
31
catch ex as OleDbException
32
objTrans.RollBack()
33
Label1.Text = ex.Message & "<p>" & _
34
"
ś
adna operacja nie została wykonana."
35
finally
36
objCmd.Connection.Close()
37
end try
38
end sub
39
</script>
40
41
<html><body>
42
<form runat="server">
43
<asp:Label id="Label1" runat="server"
44
maintainstate=false/>
45
</form>
46
</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
1
<%@Page Language="VB" %>
2
<%@Import Namespace="System.Xml" %>
3
<%@Import Namespace="System.Xml.XPath" %>
4
5
<script runat="server">
6
sub Page_Load(obj as object,e as eventargs)
7
'Tworzymy obiekt XPathDocument'
8
Dim objDocument as New XPathDocument _
9
(Server.MapPath("../rozdzial11/books.xml"))
10
end sub
11
</script>
12
13
<html><body>
14
</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
1
<%@Page Language="VB" %>
2
<%@Import Namespace="System.Xml" %>
3
<%@Import Namespace="System.Xml.XPath" %>
4
5
<script runat="server">
6
sub Page_Load(obj as object,e as eventargs)
7
Dim objDocument as New XPathDocument _
8
(Server.MapPath("../rozdzial11/books.xml"))
9
10
Dim objNav as XPathNavigator = objDocument. _
11
CreateNavigator
12
objNav.MoveToRoot()
13
DisplayTree(objNav)
14
end sub
15
16
public sub DisplayTree (objNav as XPathNavigator )
17
if (objNav.HasChildren)
18
objNav.MoveToFirstChild()
19
20
Format(objNav)
21
DisplayTree(objNav)
22
23
objNav.MoveToParent()
24
end if
25
26
while (objNav.MoveToNext())
27
Format (objNav)
28
DisplayTree (objNav)
29
end while
30
end sub
31
32
private sub Format (objNav as XPathNavigator)
33
if Not objNav.HasChildren
34
if (objNav.NodeType = XPathNodeType.Text)
35
lblMessage.Text += "" & objNav.Value & "<br>"
36
end if
37
else
38
lblMessage.Text += "<" & objNav.Name & _
39
"><br>"
40
41
if objNav.HasAttributes
42
while (objNav.MoveToNextAttribute())
43
lblMessage.Text += " <" & _
44
objNav.Name & "> " & objNav.Value & _
45
"<br>"
46
end while
47
48
objNav.MoveToParent()
49
end if
50
end if
51
end sub
52
</script>
53
54
<html><body>
55
<ASP:Label id="lblMessage" runat="server"/>
56
</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
1
<%@Page Language="VB" %>
2
<%@Import Namespace="System.Xml" %>
3
<%@Import Namespace="System.Xml.XPath" %>
4
5
<script runat="server">
6
sub SelectData(obj as object,e as eventargs)
7
Dim objDocument as New XPathDocument _
8
(Server.MapPath("../rozdzial11/books.xml"))
9
10
Dim objNav as XPathNavigator = objDocument.CreateNavigator
11
12
lblMessage.Text = ""
13
try
14
dim objIterator as XPathNodeIterator = _
15
objNav.Select(tbQuery.Text)
16
17
While objIterator.MoveNext()
18
lblMessage.Text += "<" & _
19
objIterator.Current.Name & "> " & _
20
objIterator.Current.Value & "<br>"
21
end while
22
catch ex As Exception
23
lblMessage.Text = ex.Message
24
end try
25
end sub
26
</script>
27
28
<html><body>
29
<form runat="server">
30
<h2>Zapytania XPath</h2>
31
<p>
32
Na przyład:<br>
33
<b><code>//book[last()]/@ISBN/text()</b></code> lub
34
<b><code>descendant::book/author/last-name</b></code><p>
35
36
Podaj zapytanie XPath:
37
<asp:Textbox id="tbQuery" runat=server/>
38
<asp:Button id="btnSubmit" text="Wykonaj zapytanie"
39
runat=server OnClick="SelectData"/><p>
40
<asp:Label id="lblMessage" runat=server/>
41
</form>
42
</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.
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
1
<xsl:stylesheet
2
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
3
version="1.0">
4
<xsl:template match="/">
5
<root>
6
<xsl:apply-templates/>
7
</root>
8
</xsl:template>
9
<xsl:template match="bookstore">
10
<HTML><BODY>
11
<TABLE width="450">
12
<TR>
13
<TD><b>Title</b></TD>
14
<TD><b>Price</b></TD>
15
</TR>
16
<xsl:apply-templates select="book"/>
17
</TABLE>
18
</BODY></HTML>
19
</xsl:template>
20
<xsl:template match="book">
21
<TR>
22
<TD><xsl:value-of select="title"/></TD>
23
<TD><xsl:value-of select="price"/></TD>
24
</TR>
25
</xsl:template>
26
</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
1
<%@ Page Language="VB" %>
2
<%@ Import Namespace="System.Xml" %>
3
<%@ Import Namespace="System.Xml.XPath" %>
4
<%@ Import Namespace="System.Xml.Xsl" %>
5
6
<script runat="server">
7
sub Page_Load(obj as object, e as eventargs)
8
Dim objDocument as New XPathDocument _
9
(Server.MapPath("../rozdzial11/books.xml"))
10
11
Dim objNav as XPathNavigator = _
12
objDocument.CreateNavigator
13
14
Dim objXslT As XslTransform = New XslTransform()
15
dim objWriter as XmlTextWriter = new XmlTextWriter _
16
(Server.MapPath("output.html"), nothing)
17
18
try
19
objXslT.Load(Server.MapPath("books.xsl"))
20
objXslT.Transform(objNav, nothing, objWriter)
21
objWriter.Close
22
23
lblMessage.Text = "Plik został poprawnie zapisany."
24
catch ex As Exception
25
lblMessage.Text = ex.Message
26
end try
27
end sub
28
</script>
29
30
<html><body>
31
<asp:Label id="lblMessage" runat="server"
32
maintainstate=false/>
33
</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
1
<%@ Page Language="VB" %>
2
<%@ Import Namespace="System.Xml" %>
3
<%@ Import Namespace="System.Xml.XPath" %>
4
<%@ Import Namespace="System.Xml.Xsl" %>
5
6
<script runat="server">
7
sub Page_Load(obj as object, e as eventargs)
8
Dim objDocument as New XPathDocument _
9
(Server.MapPath("../rozdzial11/books.xml"))
10
11
Dim objNav as XPathNavigator = _
12
objDocument.CreateNavigator
13
Dim objXSLT As XslTransform = New XslTransform()
14
dim objReader as XmlReader
15
16
try
17
objXSLT.Load(Server.MapPath("books.xsl"))
18
objReader = objXslT.Transform(objNav, nothing)
19
While objReader.Read()
20
Response.Write("<b>" & objReader.Name & "</b> " & _
21
objReader.Value & "<br>")
22
End While
23
24
lblMessage.Text = "Plik został poprawnie zapisany."
25
catch ex As Exception
26
lblMessage.Text = ex.Message
27
end try
28
end sub
29
</script>
30
31
<html><body>
32
<asp:Label id="lblMessage" runat="server"
33
maintainstate=false/>
34
</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.