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.