16, ## Documents ##, Delphi 4 dla każdego


Rozdział 16.
Architektura baz danych widziana od strony Delphi


W tym rozdziale zapoznasz się z programowaniem baz danych w Delphi. Jeżeli wcześniej nie miałeś styczności z programowaniem baz danych, na pierwszy rzut oka zagadnienie to może wydać się przytłaczające. Postaram się wyeliminować wszelkie nieścisłości przez zaprezentowanie klarownego wizerunku labiryntu znanego jako programowanie baz danych. Nasze rozważania zaczniemy od ogólnego spojrzenia na architekturę baz danych od strony Delphi. Następnie zajmiemy się niektórymi z komponentów dostępu do baz danych.

Nie miej złudzeń: programowanie baz danych jest naprawdę sprawą skomplikowaną. Z mojej strony otrzymasz stojącą na wysokim poziomie wiedzę z zakresu programowania baz danych w Delphi, nie ma tutaj jednak mowy o zagłębianiu się w każdy szczegół.

0x01 graphic

Nie wszystkie pojęcia i komponenty omawiane w tym rozdziale odnoszą się do każdej wersji Delphi. Wersja Professional posiada większe możliwości bazodanowe w porównaniu z wersją Standard, z kolei wersja Client/Server przewyższa je obie pod tym względem.

Podstawy

Z programowaniem baz danych wiąże się cały zbiór specjalistycznych określeń: BDE, klient, serwer, ODBC, alias, SQL, zapytanie, procedura zapamiętana i wiele innych. Dobrą wiadomością jest to, że po zapoznaniu się z tymi terminami okazują się one całkiem proste. Zacznijmy od rozważań na temat samych baz danych. Kiedy słyszysz termin baza danych prawdopodobnie wyobrażasz sobie dane zapisane w formie tabeli. Tabela ta zawiera prawdopodobnie takie pola jak Imię, Nazwisko, Numer Telefonu itp. Pola wypełnione są danymi tworzącymi indywidualne rekordy w pliku bazy danych.

Jeżeli takie wyobrażenie przychodzi Ci do głowy na myśl o bazie danych, nie jesteś zbyt daleki od prawdy, chociaż określenie to nie jest to również całkowicie poprawne. Termin baza danych służy do określenia systemu obejmującego w sposób całościowy tworzenie i utrzymywanie danych. Prawdą jest, że baza może sprowadzać się do jednej tabeli, z drugiej jednak strony rzeczywiste implementacje baz danych zawierają dziesiątki lub nawet setki tabel z tysiącami lub milionami rekordów. Tabele te mogą zawierać jeden lub więcej indeksów. Kompletne rozwiązanie bazy danych SQL typu klient/serwer może również składać się z licznych zapytań i procedur zapamiętanych (niektóre z tych terminów zostaną omówione w dalszej części rozdziału). Jak więc widzisz, baza danych to coś więcej niż tylko tabela z danymi.

Zatrzymajmy się przez chwilę przy tabelach na nich i omówmy związane z nimi podstawowe zagadnienia. Tabela składa się przynajmniej z dwóch elementów: pól i rekordów. Pola są indywidualnymi kategoriami danych w tabeli. Przykładowo, tabela zawierająca spis adresów mogłaby składać się z takich pól jak Imie, Nazwisko, Adres, NumerTelefonu itd. Pola są również określane mianem kolumn. Rekordem w takim przypadku jest pełny adres osoby: imię, nazwisko, adres i inne. Rekordy są również nazywane wierszami.

Baza danych to po prostu zbiór danych, mimo tego dosyć często bazy danych są wyświetlane w formie przypominającej arkusz kalkulacyjny. Nagłówki kolumn wskazują nazwy pól. Każdy wiersz w tabeli reprezentuje kompletny rekord. Przykładowa baza danych w takim formacie przedstawiona została na rysunku 16.1.

Rysunek 16.1.

Typowa tabela bazy danych

0x01 graphic

0x01 graphic

Wskaźnik do bieżącego rekordu w ramach bazy danych nazywany jest kursorem.

Polecenie odczytu danych z tabeli odnosi się do jej bieżącego rekordu; podobnie - polecenie zapisania określonych danych (pochodzących na przykład z formularza edycyjnego) spowoduje zapis do bieżącego rekordu. Status bieżącego rekordu ulega przemieszczeniu do innych rekordów w następstwie poruszania się po tabeli, wstawiania lub usuwania rekordów itp.

0x01 graphic

Mówiąc, że kursor jest wskaźnikiem nie mam tutaj na myśli wskaźnika (ang. pointer) w sensie Object Pascala. Stwierdzam jedynie, że jest to element informujący o bieżącej pozycji rekordu.

0x01 graphic

Określona ilość danych zawracanych przez bazę danych nazywana jest zbiorem danych (ang. dataset)

Zbiór danych może być czymś więcej niż tylko zestawem danych zawartych w tabeli - może on być bowiem np. rezultatem zapytania zawierającym dane zebrane z wielu tabel. Przykładowo załóżmy, że posiadasz bazę danych zawierającą nazwiska i adresy klientów, ich zamówienia, oraz szczegóły każdego z tych zamówień. Dane te mogą być przechowywane w tabelach o nazwach Klienci, Zamówienia i Szczegóły Zamówień. Przyjmijmy teraz, że żądasz szczegółów dotyczących 10 ostatnich zamówień złożonych przez firmę X Dane zawierające te informacje mogą pochodzić z tabel Klienci, Zamówienia i Szczegóły Zamówień; mimo, że dane pochodzą z kilku różnych tabel, prezentowane są w postaci pojedynczej tabeli (której struktura nie znajduje odzwierciedlenia w żadnej z tabel źródłowych - przyp. red.).

Lokalne bazy danych

Najprostszy typ bazy danych to lokalna baza danych. Lokalna baza danych jest bazą rezydującą na pojedynczym komputerze. Wyobraź sobie, że posiadasz program, który musi przechowywać listę nazwisk i adresów. Do przechowania tego typu danych można stworzyć bazę lokalną. Jej budowa oparta zostałaby prawdopodobnie na jednej tabeli, do której dostęp miałby jedynie Twój program; nikt inny nie posiadałby dostępu do niej. Wszelkie zamiany byłyby zapisywane bezpośrednio do bazy danych. Zwykle lokalnymi bazami danych są bazy tworzone przez takie aplikacje jak Access, dBASE, czy Paradox.

Bazy danych typu klient/serwer

Innym sposobem implementacji bazy danych jest baza danych typu klient/serwer. Sama baza danych jest tworzona i utrzymywana na serwerze plików, dostęp zaś do niej posiada jeden lub więcej użytkowników (klientów) zazwyczaj rozproszonych po sieci. Ponieważ na ogół żaden z użytkowników-klientów nie jest (a przynajmniej - nie musi być) świadom istnienia pozostałych, powstaje problem poprawnej obsługi bazy danych w warunkach jednoczesnych żądań kilku użytkowników - z którym to zadaniem uporać się musi właśnie serwer.

Użytkownicy baz typu klient/serwer niemal nigdy nie współpracują z bazą danych w sposób bezpośredni. Zamiast tego kontaktują się z nią poprzez aplikacje umieszczone na ich lokalnych komputerach. Aplikacje te, noszące nazwę aplikacji-klientów, zapewniają przestrzeganie określonych reguł i powstrzymywanie się od wykonywania operacji, które w tych warunkach nie powinny być przeprowadzane, pod groźbą uszkodzenia bazy.

Jedno-, dwu- i wielowarstwowa architektura bazy danych

Lokalne bazy danych zazwyczaj nazywane są jednowarstwowymi bazami danych. Jednowarstwowa baza danych to tak baza w której dowolne zmiany - takie jak edycja danych, wstawianie lub usuwanie rekordów - wykonywane są natychmiastowo. Program posiada jedno bezpośrednie połączenie z bazą danych.

Serwery baz danych

Ponieważ zajmujemy się bazami danych typu klient/serwer, zatrzymajmy się na chwilę przy serwerach baz danych. Serwery baz danych dzielą się na kilka kategorii. Producentami najbardziej popularnych są między innymi InterBase (będący własnością firmy Borland), Oracle, Sybase, Informix i Microsoft. Kiedy pewna firma zakupuje serwer bazy danych, oprócz niego kupuje również licencję określającą maksymalną liczbę użytkowników, jaka może korzystać z serwera. Licencjonowani użytkownicy są często określani mianem stanowisk (ang. seats). Powiedzmy, że firma zakupuje serwer InterBase oraz licencję na 50 stanowisk. Jeżeli firma ta rozwinie się do tego stopnia, że dostępu do bazy danych wymagać będzie 75 użytkowników, firma będzie musiała rozszerzyć (czytaj - dokupić) licencję na dodatkowe 25 stanowisk. Inne rozwiązanie baz danych typu klient/serwer opiera się na połączeniach. Firma może zakupić licencję na 50 równoczesnych połączeń. W samej firmie może być 1000 użytkowników bazy danych, ale tylko 50 z nich może jednocześnie połączyć się z bazą danych. Bez wątpienia, rynek serwerów baz danych stanowi ogromny biznes

W dwuwarstwowej bazie danych aplikacja-klient porozumiewa się z serwerem bazy danych poprzez sterowniki bazy danych. Zarządzanie połączeniami bierze na siebie serwer, natomiast aplikacja-klient w większym stopniu odpowiada za wpisywanie do bazy prawidłowych informacji. Spory ciężar jest nakładany na aplikację-klienta, która musi zapewnić utrzymanie integralności bazy danych.

W wielowarstwowej architekturze typu klient/serwer, aplikacja-klient porozumiewa się z jedną lub kilkoma aplikacjami-serwerami, które z kolei porozumiewają się z serwerem bazy danych. Owe pośrednie poziomy komunikacji nazywane są aplikacjami-serwerami, ponieważ udostępniają one określone usługi na rzecz aplikacji-klientów. Jedna z aplikacji-serwerów może działać jako dostawca danych, reagujący na żądania danych zgłaszane przez klientów i przekazujący je do bazy danych. Inna aplikacja-serwer może zajmować się jedynie zagadnieniami związanymi z ochroną danych.

Aplikacje-klienci pracują na maszynach lokalnych; aplikacja-serwer jest zwykle umieszczana na serwerze, sama baza danych może znajdować się na jeszcze innym serwerze. Ideą architektury wielowarstwowej jest to, że zadania spełniane przez aplikacje-klientów mogą być bardzo ograniczone ze względu na fakt, iż większość pracy wykonują za nie aplikacje-serwery. Pozwala to na tworzenie tzw. aplikacji typu cienki-klient (ang. thin-client).

Innym powodem do zastosowania architektury wielowarstwowej jest zarządzanie zasobami programistów. Aplikacje-klienci mogą być tworzone przez mniej doświadczonych programistów, ponieważ aplikacje te współpracują z aplikacją-serwerem, która sama kontroluje bazę danych. Aplikacja-serwer może zostać napisana przez bardziej doświadczonego programistę, który zna zasady na jakich musi opierać się baza danych. Ujmując to inaczej, aplikacja-serwer jest tworzona przez programistów których zadaniem jest ochrona danych przed możliwymi uszkodzeniami powodowanymi przez niedoskonałe aplikacje-klientów.

Chociaż zawsze istnieją pewne wyjątki, większość lokalnych baz danych korzysta z architektury jednowarstwowej. Bazy danych typu klient/serwer oparte są na architekturze dwuwarstwowej lub wielowarstwowej.

Jaki ma to wpływ na Ciebie? Większość aplikacji jakie będziesz pisał na potrzeby baz danych typu klient/serwer będą aplikacjami-klientami. Chociaż może zdarzyć się tak, że staniesz się jedynym z kilku programistów, którzy otrzymają zadanie stworzenia aplikacji od strony serwera lub aplikacji warstwy pośredniej, ja będę jednak raczej obstawiał za tym, że w głównym stopniu będziesz tworzył aplikacje-klientów. Jako twórca oprogramowania nie możesz porozumiewać się z serwerami baz danych w sposób bezpośredni. O tym, jak aplikacja Delphi komunikuje się z bazą danych, mowa jest w następnej sekcji.

Borland Database Engine

Aby umożliwić dostęp do lokalnych baz danych jak i do baz typu klient/serwer, Delphi udostępnia mechanizm BDE (Borland Database Engine). Jest to zbiór bibliotek DLL oraz narzędzi umożliwiających dostęp do szeregu różnorodnych baz danych.

Porozumienie się z bazą danych typu klient/serwer wymaga posiadania Delphi w wersji Client/Server. Razem z tą wersją dostarczane są sterowniki połączeń SQL używane przez BDE do porozumiewania się z bazami danych klient/serwer. Związek między aplikacją, BDE i bazą danych przedstawiony został na rysunku 16.2.

Rysunek 16.2.

Relacja między
bazą danych,
BDE i aplikacją

0x01 graphic

Sterowniki BDE

Rzeczą oczywistą jest, że między formatami baz danych i ich interfejsami API zachodzą znaczne różnice. Z tego powodu BDE dostarcza zbiór sterowników umożliwiających aplikacji porozumiewanie się z różnymi typami baz danych. Sterowniki dokonują translacji poleceń baz danych wysokiego poziomu (takich jak open lub post) na polecenia specyficzne dla danego typu bazy. Dzięki temu aplikacja może łączyć się z bazą danych bez znajomości szczegółów jej funkcjonowania.

To, jakie sterowniki znajdują się w Twoim systemie, zależy od posiadanej przez Ciebie wersji Delphi. Wszystkie wersje Delphi udostępniają sterownik pozwalający na łączenie się z lokalnymi bazami danych typu Paradox i dBASE. Sterownik ten, o nazwie STANDARD, udostępnia wszelkie niezbędne środki do pracy z wymienionymi lokalnymi bazami danych.

Wersja Client/Server zawiera sterowniki pozwalające na łączenie się z bazami takimi jak Sybase, Informix, InterBase i inne.

Aliasy BDE

W celu uzyskania dostępu do określonej bazy danych mechanizm BDE korzysta z tzw. aliasów. Jest to jeden z terminów który z początku może wydawać się niezrozumiały. Przy omawianiu BDE terminy alias i baza danych są często stosowane zamiennie.

0x01 graphic

Alias BDE to zbiór parametrów, które opisują połączenie z bazą danych.

Sięgając w głąb, niewiele można powiedzieć na temat aliasów. W swojej najprostszej formie, alias informuje BDE o typie sterownika jakiego należy użyć oraz o lokalizacji plików bazy danych na dysku. Są to rzeczy, które będziesz konfigurował w przypadku aliasów lokalnych baz danych. Jeżeli chodzi o bazy danych typu klient/serwer, alias zawiera również inne dane, takie jak maksymalny rozmiar danych BLOB, maksymalna liczba wierszy, tryb otwarty, czy nazwa użytkownika. Po utworzeniu aliasu dla bazy danych można używać go we własnych programach Delphi do wyboru określonej bazy danych. Jak tworzyć aliasy BDE dla własnych baz danych dowiesz się w dalszej części tego rozdziału w sekcji „Tworzenie aliasów BDE”.

Wbudowane bazy danych Delphi

Przy okazji omawiania tematu aliasów, przyjrzyjmy się pokrótce aliasom ustawionym już w systemie. Aby móc je zobaczyć, wykonaj następujące kroki:

  1. Zainicjuj nową aplikację.

  2. Przejdź do strony Data Access Palety Komponentów, wybierz komponent Table i umieść go w formularzu.

  3. W Inspektorze Obiektów wybierz właściwość DatabaseName, a następnie kliknij na przycisku rozwinięcia wyświetlając w ten sposób listę aliasów.

Po wykonaniu powyższych kroków powinieneś mieć przed sobą listę dostępnych aliasów. Jednym z elementów tej listy powinien być alias DBDEMOS - jest on instalowany razem z Delphi. Wybierz go.

0x01 graphic

Lista aliasów, które zobaczysz, zależy od kilku czynników. Po pierwsze od tego, czy posiadasz Delphi w wersji Standard, Professional, czy też Client/Server. Po drugie od tego, czy zainstalowałeś lokalny serwer InterBase. Oprócz tego na liście mogą pojawić się dodatkowe bazy danych, jeżeli zainstalowałeś u siebie C++ Builder lub inny produkt firmy Borland (np. Visual dBASE lub IntraBuilder).

Przejdź teraz do właściwości TableName i przyjrzyj się jej zawartości. Nazwy, które widzisz odnoszą się do tabel w wybranej bazie danych (czyli - w wybranym aliasie). Wybierz inny alias we właściwości DatabaseName i ponownie przyjrzyj się nazwom tabel. Przekonasz się, że jest to już inna lista.

Łączniki SQL

W wersji Client/Server Delphi do BDE dołączane są tzw. łączniki SQL. Łączniki SQL (ang. SQL Links) stanowią zbiór dodatkowych sterowników dla mechanizmu BDE. Umożliwiają one aplikacjom Delphi łączenie się z bazami danych typu klient/serwer udostępnianymi między innymi przez Oracle, InterBase, Informix, Sybase i Microsoft. Szczegóły dotyczące rozpowszechniania sterowników łączników SQL dostępne są również w pliku DEPLOY.TXT.

Lokalny serwer InterBase

Do Delphi w wersji Standard i Professional dołączana jest jednostanowiskowa kopia lokalnego serwera InterBase. Jak sama nazwa wskazuje serwer ten operuje na lokalnych bazach danych. Z kolei InterBase w wersji Client/Server jest pełnowartościową bazą danych typu klient/serwer. Głównym powodem, dla którego Delphi udostępnia lokalny serwer InterBase jest umożliwienie użytkownikom pisania aplikacji operujących na lokalnych bazach danych, aby następnie zastosować je dla baz danych typu klient/serwer bez wprowadzania zmian w programie. Daje to użytkownikom okazję do doskonalenia własnych umiejętności programistycznych z zakresu architektury klient/serwer, bez potrzeby wydawania pieniędzy na bazę danych tego typu.

Przy każdej próbie odwołania się do tabeli lokalnego serwera InterBase, zarówno w czasie projektowania jak i w czasie pracy programu, będziesz proszony o podanie nazwy użytkownika oraz hasła. Lokalny administrator InterBase nosi nazwę SYSDBA, a jego hasło brzmi masterkey. Można zalogować się przy użyciu tych ustawień lub przejść do menedżera serwera InterBase (InterBase Server Manager) i zarejestrować się jako nowy użytkownik systemu InterBase.

Bazodanowe komponenty Delphi

Poprzednia sekcja nie zawiera treści, które byłyby w stanie przykuć uwagę czytelnika na całą noc. Wiadomości te są jednak niezbędne, aby dobrze zrozumieć wszystkie elementy składające się na bazę danych. Dzięki tym podstawom możesz teraz przenieść swoją uwagę na komponenty VCL związane z bazami danych i przyjrzeć się, w jaki sposób ich wzajemna współpraca tworzy aplikację bazodanową. Na początku przyjrzymy się ogólnie w/w komponentom, aby później zająć się szczegółowo indywidualnymi klasami i komponentami.

Bazodanowe komponenty VCL dzielą się na dwie kategorie: niewidoczne komponenty dostępu do baz danych i wizualne komponenty prezentacji danych. Ujmując to prościej, komponenty niewidoczne zapewniają mechanizmy umożliwiające dostęp się do danych, a komponenty wizualne umożliwiają ich prezentację i edycję. Komponenty dostępu do danych wywodzą się z klasy TDataSet - zaliczają się do nich między innymi TTable, TQuery i TStoredProc. W skład wizualnych komponentów prezentacji danych wchodzą m. in. TDBEdit, TDBListBox, TDBGrid, TDBNavigator i inne. Komponenty te pracują w sposób przypominający standardowe komponenty edycji, pola listy, czy siatki (ang. grids) z tą różnicą, że są one przywiązane do określonej tabeli lub pola w tabeli. Dokonując edycji danych w ramach jednego z komponentów prezentacji, w rzeczywistości dokonujemy również edycji odpowiedniego fragmentu (rekordu) bazy danych.

0x01 graphic

Wszystkie komponenty bazodanowe VCL mogą być określane ogólnym mianem komponentów danych (ang. data components). W przypadku niewidocznych komponentów bazodanowych, umieszczonych na stronie Data Access Palety Komponentów stosowany jest termin „komponenty dostępu do danych” (ang. data access components), natomiast wizualne komponenty danych pochodzące ze strony Data Controls określane są mianem komponentów wrażliwych na dane (ang. data-aware components).

Co ciekawe, te dwie grupy komponentów nie są w stanie porozumiewać się ze sobą. Zamiast tego komunikacja między komponentami klasy TDataSet i komponentami prezentacji danych odbywa się przez pośrednika, jakim jest komponent TDataSource. Zależność ta została przedstawiona na rysunku 16.3.

Rysunek 16.3.

Architektura bazodanowych komponentów VCL

0x01 graphic

Niedługo przyjrzysz się z bliska tym komponentom, wcześniej jednak wykonasz szybkie ćwiczenie, które zilustruje opisany wyżej związek. Utwórz nową aplikację w Delphi, a następnie wykonaj kolejne kroki:

  1. Do formularza dodaj komponent Table.

  1. Znajdź właściwość DatabaseName w Inspektorze Obiektów i wybierz bazę danych DBDEMOS.

  2. Znajdź właściwość TableName i wybierz tabelę ANIMALS.DBF.

  3. Umieść w formularzu komponent DataSource i ustaw jego właściwość DataSet na Table1 (wybierz tę wartość z listy rozwijalnej) w wyniku czego źródło danych zostanie połączone ze zbiorem danych (komponentem Table).

  4. Dodaj do formularza komponent DBGrid i zmień jego właściwość DataSource na DataSource1. W ten sposób wizualny komponent prezentacji zostanie połączony ze źródłem danych i (pośrednio) ze zbiorem danych.

  5. Teraz wybierz ponownie komponent Table umieszczony w formularzu. Jego właściwości Active nadaj wartość True. W tabeli powinny pojawić się dane.

Nie było to trudne, ale to jeszcze nie koniec. Zauważ przy okazji, że paski przewijania tabeli działają nawet w czasie projektowania. Do wykonania pozostały jeszcze dwa kroki:

  1. Umieść w formularzu komponent DBImage i zmień właściwość DataSource na DataSource1 oraz właściwość DataField na BMP (BMP jest nazwą pola w tabeli ANIMALS.DBF zawierającego rysunki zwierząt). Zmień rozmiar komponentu według własnego uznania.

  1. Umieść w formularzu komponent DBNavigator i zmień właściwość DataSource na DataSource1.

Uruchom teraz program. Kliknij na dowolnym z przycisków komponentu DBNavigator. Kiedy wybierzesz przycisk Next Record, wskaźnik bieżącego rekordu ulegnie zmianie w tabeli DBTable, a to pociągnie za sobą zmianę rysunku w komponencie DBImage.

Cały proces nie wymagał napisania ani jednej linijki kodu.

Do łączenia się z bazą danych i z określoną tabelą wewnątrz bazy danych wykorzystywane są komponenty dostępu do danych. Do połączenia się z tabelą bazy danych służy komponent Table. Jest to najprostszy sposób dostępu do danych zawartych w tabeli.

Komponent Query pozwala na dostęp do tabel bazy danych poprzez wykorzystanie wyrażeń języka SQL (Structured Query Language). SQL daje większe możliwości dostępu do tabel bazy danych, ale jest też bardziej skomplikowany. Aby uzyskać dostęp do danych będziesz korzystał albo z komponentu Table albo z komponentu Query, ale nigdy z obu jednocześnie. Innym komponentem umożliwiającym dostęp do bazy danych poprzez tzw. procedury zapamiętane (ang. stored procedures) jest StoredProc. Procedura zapamiętana jest to zbiór wyrażeń SQL, które wykonują jedną lub więcej czynności na bazie danych. Procedury zapamiętane są zwykle stosowane w przypadku serii poleceń często wykonywanych na bazie danych.

Klasa TDataSet

TDataSet jest przodkiem klas TTable, TQuery i TStoredProc. W związku z tym, większość właściwości, metod i zdarzeń, z jakich korzystają te trzy klasy, jest definiowana w rzeczywistości przez klasę TDataSet. Ponieważ większość cech charakterystycznych klas potomnych ma swoje źródło w klasie TDataSet, poniżej umieszczona została lista jej podstawowych właściwości, metod i zdarzeń, a później wymienione są właściwości, metody i zdarzenia specyficzne dla każdej ze wspomnianych klas potomnych.

Najczęściej stosowane właściwości klasy TDataSet przedstawione zostały w tabeli 16.1, tabela 16.2 prezentuje główne metody tej klasy; z kolei w tabeli 16.3 znajdują się podstawowe właściwości klasy TDataSet.

Tabela 16.1. Podstawowe właściwości klasy TDataSet

Właściwość

Opis

Active

Po ustawieniu na wartość True otwiera zbiór danych, natomiast przy ustawieniu na False zamyka ten zbiór.

AutoCalcFields

Określa czy pola obliczane powinny być obliczane automatycznie.

Bof

Zwraca wartość True jeżeli kursor znajduje się na pierwszym rekordzie w zbiorze danych i wartość False w przeciwnym wypadku.

CachedUpdates

Przy wartości True wszelkie uaktualnienia będą zapisywane w buforze komputera klienta, aż do momentu całkowitego zakończenia transakcji. Przy wartości False wszelkie zmiany w bazie danych są wykonywane rekord po rekordzie.

CanModify

Określa, czy użytkownik może dokonywać edycji danych zbioru danych.

Tabela 16.1. cd. Podstawowe właściwości klasy TDataSet

Właściwość

Opis

DataSource

Wskazuje komponent DataSource skojarzony ze zbiorem danych.

DatabaseName

Nazwa aktualnie używanej bazy danych (aliasu).

Eof

Zwraca wartość True jeżeli kursor znajduje się na końcu pliku i False w przeciwnym wypadku.

FieldCount

Liczba pól w zbiorze danych. Ponieważ zbiór danych może mieć
charakter dynamiczny (dotyczy to wyników zapytań), liczba pól może zmieniać się pomiędzy kolejnymi żądaniami.

Fields

Tablica obiektów TFields zawierająca informację o polach
w zbiorze danych.

FieldValues

Zwraca wartość określonego pola w bieżącym rekordzie. Wartość jest reprezentowana w postaci typu Variant.

Filter

Wyrażenie określające kryterium, zgodnie z którym dany rekord zbioru danych jest widoczny dla użytkownika.

Filtered

Określa, czy ma zastosowanie filtrowanie rekordów. Przy wartości True zbiór danych jest filtrowany na podstawie właściwości Filter oraz zdarzenia OnFilterRecord. Przy wartości widoczne są wszystkie rekordy zbioru.

FilterOptions

Określa w jaki sposób stosowane są filtry.

Found

Informuje o powodzeniu lub niepowodzeniu operacji szukania.

Handle

Uchwyt kursora BDE do zbioru danych. Stosowany jedynie w przypadku bezpośrednich odwołań do BDE.

Modified

Określa, czy bieżący rekord został zmodyfikowany.

RecNo

Numer bieżącego rekordu w zbiorze danych.

RecordCount

Liczba rekordów w zbiorze danych.

State

Zwraca bieżący stan zbioru danych (dsEdit, dsBrowse, dsInsert itd.)

UpdateObject

Wskazuje komponent TupdateObject używany w przypadku
buforowanego uaktualniania bazy danych.

UpdatePending

Wartość True oznacza, że bufor uaktualnień zawiera zmiany
nie wprowadzone jeszcze do bazy danych.

Tabela 16.2. Podstawowe metody klasy TDataSet

Metody

Opis

Append

Tworzy pusty rekord i dołącza go na końcu zbioru danych.

AppendRecord

Dodaje rekord z wypełnionymi polami danych na końcu zbioru danych i wysyła jego wartości do bazy danych.

cd. na następnej stronie

Tabela 16.2. cd. Podstawowe metody klasy TDataSet

Metody

Opis

ApplyUpdates

Nakazuje bazie danych wprowadzenie wszelkich oczekujących zmian. Zmiany te są w rzeczywistości zapisywane dopiero w chwili wywołania funkcji CommitUpdates.

Cancel

Anuluje wszelkie zmiany dokonane w bieżącym rekordzie, o ile nie zostały one jeszcze wysłane do bazy danych.

CancelUpdates

Anuluje wszelkie zmiany oczekujące w buforze.

ClearFields

Czyści zawartość wszystkich pól bieżącego rekordu.

CommitUpdates

Zmusza bazę danych do wykonania wszystkich uaktualnień i czyści bufor uaktualnień.

Close

Zamyka zbiór danych.

Delete

Usuwa bieżący rekord.

DisableControls

Dezaktywuje wszystkie kontrolki wprowadzania związane ze zbiorem danych.

Edit

Wprowadza bieżący rekord w stan edycji.

EnableControls

Udostępnia wszystkie kontrolki wprowadzania związane ze zbiorem danych.

FetchAll

Pobiera wszystkie rekordy od miejsca wskazywanego przez kursor do końca zbioru danych i zapisuje je w sposób lokalny.

FieldByName

Pobiera nazwę pola i zwraca odpowiadający mu wskaźnik do klasy Tfield.

FindFirst

Znajduje pierwszy rekord czyniący zadość bieżącym kryteriom filtrowania.

FindNext

Znajduje kolejny rekord czyniący zadość bieżącym kryteriom filtrowania.

FindLast

Znajduje ostatni rekord czyniący zadość bieżącym kryteriom filtrowania.

FindPrior

Znajduje poprzedni rekord czyniący zadość bieżącym kryteriom filtrowania.

First

Przesuwa kursor na pierwszy rekord w zbiorze danych.

FreeBookmark

Usuwa zakładkę ustawioną wcześniej przy użyciu funkcji GetBookmark i zwalania przydzieloną jej pamięć.

GetBookmark

Ustawia zakładkę na bieżącym rekordzie.

GetFieldNames

Wyszukuje listę nazw pól w zbiorze danych.

GotoBookmark

Umieszcza kursor na rekordzie wskazywanym przez określoną zakładkę.

Insert

Wstawia rekord i ustawia dane w tryb edycji.

InsertRecord

Wstawia rekord do zbioru danych i wypełnia jego pola wartościami oraz zapisuje je do bazy danych.

Last

Umieszcza kursor na ostatnim rekordzie w bazie danych.

Locate

Przeszukuje zbiór danych w poszukiwaniu określonego rekordu.

Lookup

Lokalizuje rekord w najszybszy możliwy sposób i zwraca zawarte w  nim dane.

Tabela 16.2. cd. Podstawowe metody klasy TDataSet

Metody

Opis

MoveBy

Przesuwa kursor o określoną liczbę wierszy.

Next

Przemieszcza kursor do następnego rekordu.

Open

Otwiera zbiór danych.

Post

Zapisuje poddane edycji dane do bazy danych lub do bufora modyfikacji.

Prior

Przemieszcza kursor do poprzedniego rekordu.

Refresh

Uaktualnia zbiór danych informacjami z bazy danych.

RevertRecord

Jeżeli aktywny jest mechanizm buforowania uaktualnień, cofa zmiany wprowadzone do rekordu, ale nie zapisane jeszcze do bazy danych.

SetFields

Ustawia wartości wszystkich pól w rekordzie.

UpdateStatus

Zwraca bieżący status uaktualniania gdy dostępny jest mechanizm buforowania uaktualnień.

Tabela 16.3. Podstawowe zdarzenia klasy TDataSet

Zdarzenie

Opis

AfterCancel

Generowane po anulowaniu procesu edycji rekordu.

AfterClose

Generowane w chwili zamykania zbioru danych.

AfterDelete

Generowane po usunięciu rekordu ze zbioru danych.

AfterEdit

Generowane po przeprowadzeniu edycji rekordu.

AfterInsert

Generowane po wstawieniu rekordu do bazy danych.

AfterOpen

Generowane po otwarciu zbioru danych.

AfterPost

Generowane po wysłaniu do bazy danych zmian wprowadzonych do rekordu.

BeforeCancel

Generowane przed anulowaniem procesu edycji.

BeforeClose

Generowane przez zamknięciem zbioru danych.

BeforeDelete

Generowane przed usunięciem rekordu.

BeforeEdit

Generowane przed przejściem zbioru danych w tryb edycji.

BeforeInsert

Generowane przed wstawieniem rekordu.

BeforeOpen

Generowane tuż przed otwarciem zbioru danych (między ustawieniem właściwości Active na wartość True, a rzeczywistym otwarciem zbioru danych).

BeforePost

Generowane przed wysłaniem wprowadzonych zmian do bazy danych (lub bufora).

OnCalcFields

Generowane w chwili ustalania wartości pola obliczalnego.

cd. na następnej stronie

Tabela 16.3. cd. Podstawowe zdarzenia klasy TDataSet

Zdarzenie

Opis

OnDeleteError

Generowane jeżeli podczas usuwania rekordu pojawi się błąd.

OnEditError

Generowane jeżeli podczas edycji rekordu pojawi się błąd.

OnFilterRecord

Generowane zawsze podczas dostępu do nowego rekordu, gdy właściwość Filter jest ustawiona na wartość True.

OnNewRecord

Generowane w chwili dodania nowego rekordu do zbioru danych.

OnPostError

Generowane, gdy w chwili przesyłania modyfikacji do rekordu
wystąpi błąd.

OnUpdateError

Generowane, gdy podczas zapisywania uaktualnień z bufora do bazy danych wystąpi błąd.

OnUpdateRecord

Generowane w chwili zapisywania uaktualnionych danych do rekordu.

Edytor Pól

Edytor Pól umożliwia określenie (na etapie projektowania) zestawu pól, które dany komponent dostępu do zbioru (TTable, TQuery lub TStoredProc) udostępniać będzie -mówiąc ogólnie - do wykorzystania, między innymi (choć nie tylko) komponentowi TDataSource (a za jego pośrednictwem - komponentom prezentacyjnym).

Aby uruchomić Edytor Pól wystarczy kliknąć prawym przyciskiem myszy na danym komponencie Table, Query lub StoredProc (umieszczonym w formularzu) i wybrać polecenie Fields Editor z menu kontekstowego. Na początku okno edytora jest puste, co oznacza udostępnienie wszystkich pól; za pomocą polecenia Add fields menu kontekstowego edytora pól możesz wybierać dodawać do niego żądane pola - również po kilka pozycji na raz (Ctrl+Click). Masz również możliwość definiowania nowych pól - służy do tego polecenie New Field menu kontekstowego.

Wygląd okna Edytora Pól przedstawia rysunek 16.4.

Rysunek 16.4.

Edytor Pól

0x01 graphic

Po dodaniu pól do zbioru danych możesz kliknąć na dowolnym z nich i zmodyfikować jego właściwości za pomocą Inspektora Obiektów. Zmiany mogą obejmować format wyświetlania, ograniczenia (constraints), wyświetlane etykiety lub inne cechy pól.

Buforowanie uaktualnień

Buforowanie uaktualnień (ang. updates caching) pozwala określić czas, w którym zmiany będą wprowadzane do bazy danych; kontrolę nad tym mechanizmem sprawuje właściwość CachedUpdates. Kiedy buforowanie uaktualnień jest dozwolone, zmiany wprowadzane do rekordów nie są zapisywane bezpośrednio do bazy danych, lecz wpisywane do specjalnego bufora komputera lokalnego. Rekordy są przechowywane w tym buforze do momentu, kiedy wywołasz metodę ApplyUpdates. Wywołanie metody CancelUpdates spowoduje natomiast zignorowanie wszelkich zmian przechowywanych w buforze. Anulowanie zmian wprowadzonych do bieżącego rekordu jest możliwe dzięki metodzie RevertRecord.

Kiedy buforowanie zmian jest wyłączone (właściwość CachedUpdates ustawiona jest na wartość False), wszelkie zmiany wykonane na rekordzie są zapisywane bezpośrednio do bazy danych w chwili, kiedy kursor przesunie się na inny rekord. Jest to dobra metoda dla lokalnych baz danych, jednak z wielu względów nie zdaje ona egzaminu w przypadku baz typu klient/serwer. Dosyć często daje się słyszeć opinie, iż podstawowym powodem stosowania buforowania uaktualnień jest nadmierny ruch w sieci. Chociaż z całą pewnością prawdziwe jest stwierdzenie, iż buforowanie zmniejsza natężenie ruchu w sieci, wartość mechanizmu buforowania uaktualnień wykracza znacznie poza tego typu zastosowanie.

Wiele baz danych typu klient/serwer w wyniku zapytania zwraca rezultat w postaci „tylko do odczytu”. Jedną z zalet buforowania uaktualnień jest to, że klient może pracować z lokalną kopią zbioru danych, modyfikować ją w miarę potrzeb, a następnie zapisywać wszystkie zmiany jednocześnie do bazy danych. Jest to możliwe, ponieważ serwer bazy danych obsługuje operacje uaktualniania, wstawiania i usuwania rekordów pochodzących ze zbiorów danych możliwych jedynie do odczytywania. Lokalna baza danych musi zablokować rekordy jeżeli w danej chwili są one edytowane. Kiedy rekord jest zablokowany, inni użytkownicy bazy danych nie mają do niego dostępu. Dzięki zastosowaniu buforowanego uaktualniania czas blokowania rekordu jest znacznie skrócony.

Inną zaletą uaktualniania buforowanego jest to, że użytkownik może dokonać licznych modyfikacji w zbiorze danych, a następnie utrwalić je w całości (ang. commit) lub wycofać w całości (ang. rollback). Istnieje jednak również druga strona medalu - jeżeli okaże się, że coś pójdzie nie tak w trakcie zapisywania zmian do bazy danych na serwerze, wszystkie zmiany zostaną utracone.

Jednym z minusów buforowania uaktualnień jest to, że wielu użytkowników może pracować jednocześnie nad tym samym rekordem. W efekcie zmian dokona ten, kto wygra wyścig o dostęp do rekordu. W rzeczywistości, problem ten jest redukowany w pewnym stopniu przez zaimplementowanie określonych mechanizmów w aplikacji klienta sprawdzających, czy na rekordzie nie dokonano równoległych zmian. Przykładowo - Jaś pobiera rekord do modyfikacji, to samo czyni Marysia i obydwoje dokonują w nim zmian, na ogół innych. Marysia jako pierwsza zapisuje rekord do bazy - kiedy natomiast będzie to chciał uczynić Jan, system obsługi bazy danych poinformuje go, iż w międzyczasie rekord w bazie został już zmodyfikowany, a więc konieczny jest jego ponowny odczyt i ponowna edycja.

Komponent Table

Komponent Table, reprezentowany przez klasę TTable, zapewnia najszybszy i najprostszy dostęp do tabeli. Tabele są więcej niż wystarczające w przypadku większości jednowarstwowych aplikacji baz danych. Komponent Table jest zazwyczaj stosowany przy obsłudze lokalnych baz danych, a komponent Query znajduje zastosowanie w bazodanowych serwerach SQL.

Klasa TTable posiada wiele właściwości i metod, oprócz tych dziedziczonych od swojego przodka TDataSet. Podstawowe właściwości klasy TTable przedstawione zostały w tabeli 16.4, natomiast główne metody tej klasy znalazły się w tabeli 16.5. Są to metody i właściwości specyficzne dla klasy TTable, nie ma wśród nich elementów odziedziczonych od TDataSet.

W większości wypadków, metody i właściwości są niezwykle intuicyjne. Oznacza to, że zazwyczaj przeznaczenie metody lub właściwości może być wywnioskowane na podstawie jej nazwy. Łatwo można na przykład wydedukować, że metoda LockTable

Tabela 16.4. Podstawowe właściwości charakterystyczne dla klasy TTable

Właściwość

Opis

Exclusive

Blokuje lokalną tabelę dla wyłącznego użytku przez daną aplikację.

IndexDefs

Zawiera informacje o indeksach tabeli.

IndexFieldCount

Liczba pól tworzących bieżący klucz.

IndexFieldNames

Wykorzystywana do ustawienia bieżącego klucza przez wyspecyfikowanie nazw pól przeznaczonych do utworzenia indeksu.

IndexFields

Używana do odtworzenia informacji na temat specyficznego pola
wchodzącego w skład indeksu.

IndexName

Służy do wyspecyfikowania drugorzędnego indeksu dla tabeli.

KeyFieldCount

Liczba pól jaką należy użyć przy szukaniu po kluczu częściowym.

MasterFields

Pole (lub pola) używane do łączności pomiędzy tabelą główną (master) i szczegółową (details).

MasterSource

Wskazanie na komponent DataSource powiązany z tabelą pełniącą rolę tabeli głównej (master) dla niniejszej tabeli.

ReadOny

Określa czy dana tabela jest otwarta tylko do odczytu.

TableName

Nazwa tabeli w bazie danych.

TableType

Typ tabeli (Paradox, dBASE lub ASCII).

Tabela 16.5. Podstawowe metody charakterystyczna dla klasy TTable

Właściwość

Opis

AddIndex

Tworzy nowy indeks dla tabeli.

ApplyRange

Aktywuje mechanizm ograniczenia zakresu (range) na zbiór danych. Mechanizm ten polega na narzuceniu wartości granicznych na każde z pól użytych do indeksowania - tylko te rekordy, dla których wartości każdego z tych pól znajdują się w dopuszczalnych granicach, mogą być wyświetlane i edytowane.

BatchMove

Przenosi rekordy ze zbioru danych do tabeli.

CancelRange

Usuwa ograniczenia wynikające z mechanizmu zakresów.

CreateTable

Odtwarza tabelę na podstawie podanych informacji.

DeleteIndex

Usuwa drugorzędny indeks.

DeleteTable

Usuwa tabelę.

EmptyTable

Usuwa wszystkie rekordy z tabeli.

GetIndexNames

Dostarcza listę wszystkich indeksów tabeli.

GotoKey

Przenosi kursor do rekordu wskazywanego przez bieżący klucz.

GotoNearest

Przenosi kursor do rekordu najlepiej pasującego do bieżącego klucza.

LockTable

Blokuje tabelę tak, że inne aplikacje nie mają do niej dostępu.

RenameTable

Zmienia nazwę tabeli.

SetKey

Umożliwia ustawienie kluczy dla zbioru danych.

SetRange

Ustawia ograniczenia dla mechanizmu zakresów. Metoda ta wykonuje takie same operacje jak sekwencja metod SetRangeStart,
SetRangeEnd i ApplyRange.

SetRangeEnd

Informuje o rozpoczęciu definiowania („na raty”) górnych ograniczeń dla mechanizmu zakresów.

SetRangeStart

Informuje o rozpoczęciu definiowania („na raty”) dolnych ograniczeń dla mechanizmu zakresów.

UnlockTable

Odblokowuje tabelę, która została wcześniej zablokowana poleceniem LockTable.

blokuje tabelę z powodów specyficznych dla aplikacji, a metoda UnlockTable ponownie odblokowuje tę tabelę. Podobnie nie trzeba być geniuszem, aby zgadnąć do czego służą metody CreateTable, DeleteTable i RenameTable. W związku z tym opisane poniżej metod i właściwości nie będą omawiane w sposób szczegółowy, skupimy się za to na bardziej interesujących aspektach komponentu Table.

0x01 graphic

Jak się już przekonałeś, właściwość DatabaseName służy do wyboru aliasu BDE. W przypadku lokalnych baz danych można zrezygnować z wyboru aliasu, a zamiast tego wpisać nazwę katalogu w którym znajdują się pliki bazy danych. W efekcie we właściwości TableName znajdzie się lista tabel bazy danych znajdujących się w tym katalogu.

Filtry

Powszechną potrzebą aplikacji bazodanowej jest filtrowanie tabeli. Zanim przejdę do szczegółowego omówienia filtrów chcę zaznaczyć, że filtry są stosowane głównie w przypadku lokalnych baz danych. Ich wykorzystanie w bazach danych typu klient/serwer jest raczej rzadkie, gdyż równoważny efekt osiąga się za pomocą odpowiednio sformułowanych zapytań SQL.

Dlaczego więc filtr? Wyobraź sobie, że posiadasz tabelę zawierającą tysiące rekordów, podczas gdy Ciebie interesuje jedynie wyświetlenie małego ich podzbioru lub praca na małym podzbiorze tej tabeli. Powiedzmy, że dysponujesz bazą danych zawierającą nazwiska i adresy użytkowników komputerów z całego świata. Twoja firma sprzedaje te nazwiska i adresy innym firmom zajmującym się korespondencją masową.

Dzwonię i zamawiam w Twojej firmie listę adresową, ale chcę, aby znaleźli się na niej tylko użytkownicy komputerów mieszkający w Gliwicach. Możesz przefiltrować swoją tabelę biorąc pod uwagę kod pocztowy i wygenerować listę nazwisk użytkowników pochodzących jedynie z Gliwic. Być może to firma Borland zadzwoni do Ciebie prosząc o listę użytkowników w Polsce będących z zawodu programistami. W takim przypadku tabelę należałoby przefiltrować według zawodu i kraju, co w efekcie dałoby nazwiska i adresy użytkowników, którymi zainteresowanymi jest klient.

Stosowanie filtrów w komponencie Table

Filtry w komponencie Table są obsługiwane na dwa różne sposoby: poprzez właściwość Filter lub poprzez zdarzenie OnFilterRecord. Zanim do nich przejdziemy, omówię jeszcze właściwość Filtered. Właściwość ta określa, czy dana tabela w ogóle wykorzystuje mechanizm filtrowania - jeżeli jej wartością True, tabela zastosuje aktualnie obowiązujący filtr (wynikający z właściwości Filter lub będący rezultatem obsługi zdarzenia OnFilterRedord); w przypadku wartości False zawartość właściwości Filter jest ignorowana, a zdarzenie OnFilterRecord nie jest w ogóle generowane.

We właściwości Filter umieszcza się nazwę pola, operator logiczny i wartość - przykładowy filtr mógłby wyglądać następująco:

FirstName = 'Jan'

Wyrażenie mówi mniej więcej tyle: „Pokaż wszystkie rekordy w których imieniem jest Jan.” Filtry mogą również stosować słowa kluczowe AND, OR lub NOT:

CustNr = 1384 AND ShipDate < '1/1/94'

0x01 graphic

Nazwy pól i operatory logiczne (AND, OR lub NOT) są niewrażliwe na małe/ duże litery - następujące dwa wyrażenia mają identyczne znaczenie:

CustNr = 'TurboPower' and ShipDate < '1/1/94'

CUSTNR = 'TurboPower' AND SHIPDATE < '1/1/94'

W przypadku szukania tekstu o rozróżnianiu małych/dużych liter decyduje właściwość FilterOptions.

W wyrażeniach należących do filtru mogą zostać wykorzystane następujące operatory:

Operator

Znaczenie

<

Mniejszy

>

Większy

Równy

<>

Nierówny

>=

Większy lub równy

<=

Mniejszy lub równy

()

Służy do określenia porządku poleceń w wyrażeniu złożonym

[]

Stosowane jako ograniczniki dla nazw pól zawierających spacje

AND, OR, NOT

Operatory logiczne - koniunkcja, alternatywa, zaprzeczenie

Filtrowanie za pomocą właściwości Filter

Poprzednio wspomniałem, że istnieją dwa sposoby filtrowania tabeli. Jeden z nich polega na użyciu właściwości Filter. W tym celu wyrażenie filtrujące należy wpisać bezpośrednio w pole tej właściwości (za pomocą Inspektora Obiektów) na etapie projektowania lub przypisać jej odpowiednią wartość w czasie wykonania programu. Oprócz tego trzeba oczywiście nadać wartość True właściwości Filtered.

Zobrazujmy to przykładem. Po pierwsze skonfiguruj niezbędne komponenty:

  1. Umieść w formularzu komponenty Table, DataSource i DBGrid.

  1. Kliknij na komponencie Table i zmień jego właściwość DatabaseName na DBDEMOS, właściwość TableName na ORDERS.DB i właściwość Active na True.

  2. Kliknij na komponencie DataSource i zmień jego właściwość DataSet na Table1.

  3. Wybierz komponent DBGrid i zmień jego właściwość DataSource na DataSource1. Zmodyfikuj rozmiar komponentu według własnego uznania.

  4. Wpisz następującą wartość w kolumnie Value właściwości Filter:

CustNo = 1384

  1. Nadaj właściwości Filtered wartość True.

W tej chwili tabela powinna wyświetlać jedynie zamówienia dotyczące klienta nr 1384. Poeksperymentuj jeszcze trochę, zmieniając wyrażenie filtrujące i obserwując zmiany zachodzące za każdym razem w tabeli. Wypróbuj następujące wyrażenia:

CustNo = 1510
CustNo = 1384 and ShipDate < '1/1/94'
CustNo = 1384 and ShipDate > '1/1/94'
OrderNo > 1100 and OrderNo < 1125

Zmieniałeś wyrażenie w czasie projektowania, jednak bardziej prawdopodobne jest, że w przyszłości będziesz zmieniał filtr dynamicznie w czasie pracy programu - co robi się w oczywisty sposób:

Table1.Filter := 'CustNo = 1510';

0x01 graphic

Jeżeli właściwość Filtered jest ustawiona na True, ale właściwość Filter jest pusta, zwracany jest pełny zbiór danych tak, jakby tabela nie została w ogóle przefiltrowana.

Filtrowanie z wykorzystaniem zdarzenia OnFilterRecord

Innym sposobem pozwalającym na filtrowanie tabeli jest zdarzenie OnFilterRecord. Aby wygenerować procedurę obsługującą to zdarzenie, kliknij podwójnie na kolumnie Value Inspektora Obiektów obok zdarzenia OnFilterRecord. Po utworzeniu szkieletu procedury zdarzeniowej wpisz w nią kod dokonujący filtrowania. Weźmy pod uwagę pierwszy z przedstawionych wcześniej przykładów (CustNo = 1384), ale tym razem zamiast właściwości Filter skorzystajmy ze zdarzenia OnFilterRecord:

procedure TForm1.Table1FilterRecord(DataSet: TDataSet;

var Accept: Boolean);

var

Value : Integer;

begin

Value := Table1.FieldByName('CustNo').Value;

Accept := (Value = 1384);

end;

Rzeczywisty kod został podzielony na dwie części, aby uczynić go bardziej czytelnym. Kluczowym elementem jest tutaj parametr Accept. Zdarzenie OnFilterRecord jest wywoływane jeden raz dla każdego wiersza w tabeli - wynikowa wartość parametru Accept określa, czy rekord ma być widoczny (True) czy też odrzucony przez filtr (False). W powyższym fragmencie parametr tej przyjmuje wartość True wtedy i tylko wtedy, gdy zmienna Value (równa wartości pola CustNo rekordu) ma wartość 1384.

Powracając na chwilę do prezentowanych przed chwilą przykładowych łańcuchów przypisywanych właściwości Filter - równoważne instrukcje ustalające wartość parametru Accept dla dwóch pierwszych miałyby następującą postać:

Accept := Table1.FieldByName('CustNo').Value = 1510;

Accept :=

(Table1.FieldByName('CustNo').Value = 1384)

and

(Table1.FieldByName('ShipDate').AsDateTime < StrToDate('1/1/94'));

Korzystanie ze zdarzenia OnFilterRecord jest nieco bardziej skomplikowane niż korzystanie z właściwości Filter, lecz daje za to o wiele większe możliwości.

Właściwość FilterOptions

Właściwość FilterOptions określa dodatkowy aspekt filtrowania; jest on właściwością zbiorową, na którą składają się dwie opcje: foCaseInsensitive i foNoPartialCompare.

Obecność pierwszej z nich powoduje, że w wyrażeniu filtrującym nie są rozróżniane małe/duże litery. Druga ma natomiast związek z tzw. porównywaniem częściowym. Otóż, jeżeli na końcu wyrażenia filtrującego umieścimy gwiazdkę (*), to gwiazdka ta traktowana jest domyślnie jako tzw. znak blankietowy (ang. wildcard), zastępujący dowolny ciąg znaków; jeżeli więc np. określimy właściwosć Filter jako LastName :='M*', to wszystkie rekordy, w których pole LastName rozpoczyna się od litery M, uważane będą za pasujące do filtra. Włączenie do właściwości FilterOptions opcji foNoPartialCompare zmienia ten domyślny stan rzeczy - gwiazdka traktowana jest na równi z innymi znakami, a nie jako znak blankietowy.

Domyślnie właściwość FilterOptions jest pusta, co oznacza utożsamianie małych/dużych liter i traktowanie gwiazdek jako znaki blankietowe.

Wyszukiwanie rekordów

Szukanie określonych rekordów w tabeli można przeprowadzić na kilka różnych sposobów. Sekcja ta w równym stopniu odnosi się do wszystkich potomków TDataSet, a nie tylko do samej klasy TTable.

0x01 graphic

Podobnie jak w przypadku filtrów, odnajdywanie rekordów w bazie danych typu klient/serwer jest niemal zawsze wykonywane przy użyciu zapytań SQL. Szukanie rekordów przy użyciu metod klasy TTable jest operacją odnoszącą się głównie do lokalnych baz danych.

Do przeszukania odfiltrowanego zbioru danych można użyć metod FindFirst, FindNext, FindPrior i FindLast. Metody te najlepiej nadają się do szukania w przefiltrowanych zbiorach danych, ponieważ każdorazowe wywołanie dowolnej z nich wiąże się z ponownym zastosowaniem filtru. W związku z tym, jeżeli rekordy, które wcześniej nie pasowały do filtru zostały zmodyfikowane tak, że w chwili obecnej pasują do niego, zostaną włączone do zbioru danych przed wykonaniem procesu poszukiwania.

Inny sposób na przeszukanie tabeli wiąże się metodami FindKey i GotoKey. Metody te wymagają indeksu. FindKey szuka określonej wartości w polu (lub polach) aktywnego indeksu. Poniższy poszukuje klienta o numerze 1384 - przy założeniu, że indeks CustNo jest indeksem po numerze klienta:

Table1.IndexName := 'CustNo';

if not Table1.FindKey([1384])

then

MessageBox(Handle,

'Rekord nie został znaleziony','Komunikat', MB_OK);

end;

Trzecia metoda przeszukiwania tabeli opiera się na metodach Locate i Lookup. Jedną z zalet tych metod jest to, że nie wymagają one indeksów; jeżeli jednak istnieje indeks, który można by wykorzystać do wyszukiwania, obydwie metody czynią to.

Metoda Locate poszukuje rekordu na podstawie podanego zestawu pól i żądanej ich zawartości. Rezultat poszukiwania odzwierciedlany jest przez wynik funkcji - wartość True oznacza znalezienie rekordu, przy czym rekord ten staje się rekordem bieżącym; wartość False świadczy o nieznalezieniu takowego. Proces poszukiwania może być modyfikowany za pomocą parametru typu zbiorowego, zawierającego dwie poniższe opcje:

Metoda Lookup również poszukuje rekordu na podstawie żądanej zawartości żądanego zestawu pól, nie używa jednak modyfikatorów w stylu loPartialKey i loCaseInsensitive, nie zmienia też pozycji bieżącego rekordu. Ponadto, jej wynikiem jest nie wartość boolowska, lecz tablica zawierająca wartości pól, których zestaw określony jest przez odrębny parametr.

Oto przykład użycia metody Locate:

var

Options : TLocateOptions;

begin

Options := [loPartialKey]

if not Table1.Locate('CustNo', '1384', Options)

then

MessageBox(Handle, 'Rekord nie został znaleziony',

'Komunikat', MB_OK);

end;

W powyższym przykładzie poszukiwany jest rekord, którego pole CustNo rozpoczyna się od znaków 1384.

Relacje Master/Details

Komponent Table umożliwia łatwe ustanawianie związku Master/Details; zacznę od wytłumaczenia jego istoty a następnie pokażę, jak się go tworzy.

Załóżmy, że posiadasz tabelę o nazwie CUSTOMER, która zawiera informacje dotyczące klientów. Przypuśćmy dodatkowo że jest ona poindeksowana według pola o nazwie CustNo.

Załóżmy ponadto, że istnieje jeszcze jedna tabela o nazwie ORDERS zawierająca listę wszystkich zamówień złożonych przez klientów. Naturalnie, tabela ta musi również posiadać pole CustNo. Chcesz przejrzeć tabelę zawierającą wszystkich klientów - czy nie byłoby by miło, gdybyś mógł jednocześnie oglądać zamówienia złożone przez każdego z klientów? Realizację tego zadania umożliwia właśnie związek Master/Details polegający na tym, że dla każdego z rekordów tabeli głównej (master) - w tym przypadku tabeli CUSTOMER - udostępniany jest zestaw rekordów tablicy szczegółowej (details) - w tym przypadku tabeli ORDERS -spełniających określone kryterium. Kryterium to opiera się o równość dwóch pól: wyróżnionego pola tabeli głównej (nazwijmy je polem głównym) i wyróżnionego pola tabeli szczegółowej (nazwijmy je polem szczegółowym). W naszym przykładzie obydwa wyróżnione pola noszą tę samą nazwę - CustNo. Jeżeli w bieżącym rekordzie tabeli głównej wartość pola CustNo wynosi - powiedzmy - 1384, to wybranymi rekordami z tabeli szczegółowej będą te, dla których wartość w polu CustNo wynosi również 1384.

Poniższy przykład z pewnością wyjaśni tę ideę:

  1. Zainicjuj nową aplikację. Umieść komponent Table w formularzu. Ustaw jego właściwości w następujący sposób:

Właściwość

Wartość

Name

Master

DatabaseName

DBDEMOS

TableName

customer.db

  1. Umieść w formularzu komponent DataSource i ustaw jego właściwość DataSet na Master.

  2. Teraz umieść w formularzu drugi komponent Table i zmień jego nazwę (Name) na Details; ustawieniem pozostałych właściwości zajmiesz się za chwilę.

  3. Umieść w formularzu drugi komponent DataSource. Zmień jego właściwość DataSource na Details.

  4. Kliknij na tabeli Details. Zmień jej właściwości następująco:

Właściwość

Wartość

DatabaseName

DBDEMOS

TableName

orders.db

MasterSource

DataSource1

  1. Kliknij na przycisku wielokropka obok właściwości MasterFields. Otwarte zostanie okno Projektanta Połączeń (ang. Link Designer).

  2. U szczytu okna projektanta znajduje się pole listy rozwijalnej o nazwie Available Indexes. Wybierz z listy indeks CustNo.

  3. Teraz w polach obu list (Detail Fields i Master Fields) widoczne jest pole CustNo. Wybierz je w każdej z tych list i kliknij na przycisku Add, aby utworzyć związek „master/detail”. Lista pól połączonych (Joined Fields) pokazuje teraz, że dwie tabele są teraz połączone przez ich pola CustNo.

  4. Zamknij okno Projektanta Połączeń klikając na przycisku OK.

  5. Umieść w formularzu dwa komponenty DBGrid i połącz jeden z nich ze źródłem danych DataSource1 a drugi ze źródłem danych DataSource2.

  6. Zmień właściwość Active obu tabel na True. Tablica główna (ang. master) będzie wyświetlać wszystkich klientów, natomiast tabela szczegółowa (ang. details) wyświetlać będzie zamówienia każdego z klientów.

Przed chwilą utworzyłeś związek między tabelą główną i tabelą szczegółową. Związek ten połączył dwie wspomniane tabele poprzez ich wspólne pole: CustNo. Żeby w pełni zrozumieć, co to oznacza, uruchom program i zacznij poruszać się po rekordach w tabeli głównej. W chwili wybrania nazwy klienta w tabeli głównej, w tabeli szczegółowej wyświetlone zostaną zamówienia dotyczące jedynie tego klienta.

Komponent Query

Komponent Query stanowi preferowany sposób dostępu do danych w bazach typu klient/serwer. Dalsze sekcje opisują podstawowe właściwości i metody klasy TQuery.

0x01 graphic

Komponent Query w przeciwieństwie do komponentu Table nie posiada właściwości TableName. Oznacza to, że w czasie projektowania nie ma możliwości natychmiastowego obejrzenia listy tabel aktualnie wybranej bazy danych. Żeby zobaczyć tą listę można wykonać jedno z dwóch zadań. Po pierwsze, można tymczasowo umieścić w formularzu komponent Table, ustawić właściwość DatabaseName, a następnie obejrzeć listę tabel poprzez właściwość TableName. Można również wybrać komponent Query znajdujący się w formularzu, kliknąć na nim prawym przyciskiem myszy, a następnie wybrać polecenie menu kontekstowego Explore. W efekcie zostaniesz przeniesiony albo do Eksploratora SQL (wersja Client/Server) albo do Administratora BDE (wersje Standard i Professional). Obydwa te narzędzia mogą być wykorzystane do przejrzenia tabel bazy danych.

Właściwość SQL

Właściwość SQL jest listą typu TStringList zawierającą tzw. zapytania SQL (SQL queries). Wartość właściwości SQL można nadać w czasie projektowania poprzez Inspektor Obiektów bądź programowo.

Aby ustawić tę wartość na etapie projektowania, kliknij na przycisku wielokropka obok właściwości SQL w oknie Inspektora Obiektów. Otwarte zostanie okno edytora listy łańcuchów (ang. String List Editor) umożliwiające wpisanie jednego lub kilku linii zapytań SQL.

0x01 graphic

Pamiętaj, że edytor listy łańcuchów posiada opcję umożliwiającą edytowanie listy łańcuchów w Edytorze Kodu Delphi.

W przypadku dodawania zapytań do właściwości SQL w trakcie pracy programu, trzeba upewnić się, że jej poprzednia zawartość została wyczyszczona:

Query1.SQL.Clear;

Query1.SQL.Add('select * from country');

Właściwość SQL łatwo można wyobrazić sobie jako łańcuch, a nie listę łańcuchów. Jeżeli nie wyczyścimy wartości SQL przed dodaniem nowego łańcucha, poprzednie zapytania SQL pozostaną w liście łańcuchów. Przy próbie wykonania takiego wyrażenia niemal z całą pewnością pojawią się błędy.

Realizacja zapytań SQL

Zapytania zawarte we właściwości SQL zostaną wykonane w chwili wywołania metody Open lub ExecSQL. Jeżeli korzystasz z wyrażeń SQL zawierających polecenie SELECT, do wykonania zapytania SQL użyj metody Open. Natomiast gdy w wyrażeniu SQL znajdują się polecenia INSERT, UPDATE, lub DELETE, do jego wykonania należy użyć metody ExecSQL. W poniższym przykładzie ustawiana jest właściwość SQL, po czym następuje wywołanie metody Open:

Query1.SQL.Clear;

Query1.SQL.Add('select * from country');

Query1.Open;

Wyrażenie SELECT, należące do języka SQL, wydobywa określone kolumny z bazy danych. Gwiazdka stanowi polecenie (dla serwera bazy danych) zwrócenia wszystkich kolumn znajdujących się w tabeli. W związku z tym, w powyższym przykładzie baza danych zwraca kompletną tabelę o nazwie country. W celu zwrócenia jedynie określonych kolumn można użyć np. następującego kodu:

Query1.SQL.Clear;

Query1.SQL.Add('select Name,Capital from country');

Query1.Open;

0x01 graphic

Równoważne z wywołaniem metody Open jest ustawienie właściwości Active na wartość True.

Polecenie SQL DELETE usuwa rekordy z bazy danych. Usunięcie rekordu z bazy danych można przeprowadzić w następujący sposób:

Query1.SQL.Clear;

Query1.SQL.Add('delete from country where name ='Royland');

Query1.SQL.ExecSQL;

Zwróć uwagę, że zamiast metody Open użyta została metoda ExecSQL. Jak wspomniałem wcześniej, do wykonania zapytań zawierających wyrażenia INSERT, UPDATE i DELETE wykorzystywać należy właśnie metodę ExecSQL.

Polecenie INSERT wstawia rekord do bazy danych:

Query1.SQL.Add('insert into country');

Query1.SQL.Add('(Name, Capital)');

Query1.SQL.ADD('values (“Royland, “Royville")');

Query1.ExecSQL;

0x01 graphic

Przyjrzyj się poprzedniemu przykładowi - widać w nim użycie podwójnego znaku cudzysłowu. Składnia języka SQL nie powinna być mylona ze składnią języka Object Pascal. SQL pozwala na stosowanie zarówno pojedynczych, jak i podwójnych znaków cudzysłowu wokół nazw wartości. Można stosować oba rodzaje znaków, jednak w przypadku użycia pojedynczych znaków wokół łańcucha trzeba je podwoić. Oba poniższe wyrażenia są poprawne:

Query1.SQL.Add('values ("Royland","Royville")');

Query1.SQL.Add('values(''Royland'',''Royville'')');

Uaktualnianie zbioru danych przy użyciu polecenia UPDATE wygląda następująco:

Query1.SQL.Clear;

Query1.SQL.Add('update country');

Query1.SQL.Add('set Capital = ''Royburg''');

Query1.SQL.Add('where Name = "Royland"');

Query1.ExecSQL;

Moją intencją nie jest uczenie języka SQL, uznałem jednak, że kilka przykładów pomoże czytelnikowi w rozpoczęciu tej nauki.

Stosowanie parametrów w zapytaniach SQL

Wyrażenia SQL korzystają z parametrów, aby być bardziej elastycznymi. Parametr w wyrażeniu SQL przypomina w dużym stopniu zmienną języka Object Pascal. Występujący w wyrażeniu parametr jest poprzedzony znakiem dwukropka. Weźmy dla przykładu następujące wyrażenie:

select * from country where name = :Param1

Parametr w powyższym zapytaniu nosi nazwę Param1; podczas realizacji zapytania zostanie on zastąpiony konkretną wartością, przechowywaną pod właściwością Params:

Query1.SQL.Add('select * from country where Name = :Param1');

Query1.ParamByName('Param1').AsString := 'Brazil';

Query1.Open;

Właściwość Params można ustawiać zarówno na etapie projektowania, jak i w czasie wykonania programu. W powyższym przykładzie wykorzystano metodę ParamByName wyodrębniającą określony parametr na podstawie jego nazwy( tu: Param1); istnieje też drugi sposób, oparty na kolejnym indeksie parametru w ramach właściwości Params, na przykład poniższe przypisanie

Query1.Params[0].AsString := 'Brazil';

odnosi się do pierwszego parametru (parametry indeksowane są począwszy od zera). Jako że o wiele łatwiej pamięta się nazwy mnemotechniczne niż bezwzględne numery, sposób ten jest zdecydowanie mniej wygodny, podatny na błędy i wrażliwy na reorganizację właściwości Params.

0x01 graphic

Nie wszystkie elementy zapytań SQL mogą być parametryzowane - serwery SQL nie pozwalają na przykład stosowania parametru dla nazwy tabeli. Weźmy pod uwagę następujące zapytanie SQL:

select * from :TableName

Zapytanie to zostanie uznane za błędne, gdyż parametr znajduje się w miejscu, w którym spodziewana jest nazwa tabeli.

Komponent StoredProc

Komponent StoredProc reprezentuje procedurę zapamiętaną (ang. stored procedure). Procedura zapamiętana jest to zbiór wyrażeń SQL, które wykonują się jak prosty program. Są to indywidualne programy operujące na bazie danych, realizujące często wykonywane zadania bazodanowe. Jest to ułatwienie pracy dla programistów, ponieważ nie muszą oni wpisywać kodu linia po linii za każdym razem, gdy chcą wykonać określone zadanie. Cała ich praca sprowadza się wtedy jedynie do wywołania procedury zapamiętanej na serwerze.

Konsekwencją tego jest również zmniejszenie rozmiaru aplikacji-klientów, gdyż nie muszą one przechowywać w sobie zbędnego kodu.

Innym zadaniem procedur zapamiętanych jest utrzymywanie integralności danych. Integralnością danych nazywa się - w przybliżeniu - ich wewnętrzną spójność; powracając do przykładu z klientami i składanymi zamówieniami - jednym z (oczywistych) wymogów integralności jest istnienie w tabeli CUSTOMER rekordu dla każdego klienta, dla którego w tabeli ORDERS istnieje chociaż jedno zamówienie (innymi słowy - zawartość pola CustNo w każdym z rekordów tabeli ORDERS musi mieć swe pokrycie w zawartości pola CustNo w którymś rekordzie bazy CUSTOMER). Operując na bazie danych w sposób elementarny łatwo tę integralność naruszyć - na przykład usuwając rekord opisujący danego klienta, lecz pozostawiając rekordy opisujące złożone przez niego zamówienia. Ograniczenie środków operowania na bazie do kompletnych procedur eliminuje takie zagrożenie - oczywiście pod warunkiem ich poprawnej konstrukcji; dlatego też administrację procedurami zapamiętanymi powierza się z reguły administratorowi serwera.

Podobnie jak w przypadku zapytań SQL, procedury zapamiętane mogą korzystać z parametrów. Jeżeli chodzi o procedurę nie korzystającą z parametrów, jej wywołanie polega po prostu na podaniu nazwy i fizycznym wywołaniu:

StoredProc1.StoredProcName := 'DO_IT';

StoredProc1.Prepare;

StoredProc1.ExecProc;

Najpierw wywoływana jest metoda Prepare, której celem jest wstępna preparacja procedury. Następnie przychodzi kolej na metodę ExecProc, która dokonuje fizycznego wywołania procedury zapamiętanej.

W przypadku procedur parametryzowanych należy bezpośrednio przed wywołaniem ustawić wartości parametrów, na przykład:

StoredProc1.StoredProcName := 'ADD_EMP_PROJ';

StoredProc1.ParamByName('EMP_NO').Value := 12;

StoredProc1.ParamByName('PROJ_ID').Value := 'VBASE';

StoredProc1.Prepare;

StoredProc1.ExecProc;

Jeżeli posiadasz Delphi w wersji Professional lub Client/Server możesz przetestować powyższy kod wykonując następujące kroki:

  1. Umieść w formularzu komponent StoredProc i ustaw jego właściwość Database­Name na IBLOCAL.

  1. Dodaj przycisk do formularza i kliknij na nim dwukrotnie, aby utworzyć jego procedurę zdarzeniową (dla zdarzenia OnClick); w jej wnętrzu umieść kod przedstawiony powyżej.

  2. Dodaj komponent Table do formularza, ustaw jego właściwość DatabaseName na IBLOCAL, a właściwość TableName na EMPLOYEE_PROJECT. Umieść w formularzu komponenty DBGrid i DataSource i powiąż je z dodaną przed chwilą tabelą. Ustaw właściwość Active tabeli na wartość True. Dzięki temu będziesz mógł zobaczyć zmiany wprowadzone do tabeli.

  3. Na końcu kodu. który dodany został w punkcie trzecim, dodaj następującą linię kodu:

Table1.Refresh;

Teraz uruchom program. Kiedy klikniesz na przycisku, do tabeli dodany zostanie nowy rekord z identyfikatorem pracownika (EMP_NO) równym 12 i identyfikatorem projektu (PROJ_ID) równym VBASE. Zamknij program. Zmodyfikuj kod, tak aby identyfikator pracownika był równy 10 i ponownie uruchom aplikację. Tym razem procedura zapamiętana zwróci komunikat informujący iż wybrany numer pracownika jest niepoprawny. Błąd wynika z tego, iż procedura ADD_EMP_PROJ sprawdza dane wejściowe - i nie znajduje pracownika o numerze 10.

Komponent UpdateSQL

Komponent UpdateSQL udostępnia sposób na wprowadzanie modyfikacji do zbioru danych ustawionego tylko do odczytu, gdy dostępny jest mechanizm uaktualnień buforowanych. Zazwyczaj zbiór danych tylko do odczytu jest zbiorem - dosłownie - tylko do odczytu. Niemniej jednak, kiedy dostępny jest mechanizm buforowania zmian, zbiór danych może być modyfikowany, a rezultaty tych modyfikacji zapisywane do bazy danych.

0x01 graphic

Podgląd treści procedury zapamiętanej umożliwia polecenie Explore menu kontekstowego. Procedura ADD_EMP_PROJ wygląda następująco:

CREATE PROCEDURE ADD_EMP_PROJ (

EMP_NO SMALLINT,

PROJ_ID CHAR(5)

} AS

BEGIN

BEGIN

INSERT INTO employee_project (emp_no, proj_id) VALUES (:emp_no, :proj_id);

WHEN SQLCODE -530 DO

EXCEPTION unknown_emp_id;

END

SUSPEND;

END

Nie dokonuj żadnej modyfikacji tego kodu, jeżeli nie znasz dobrze języka SQL

Większość baz danych typu klient/serwer dysponuje domyślnymi akcjami wykonywanymi w chwili gdy zajdzie potrzeba zastosowania uaktualnień znajdujących się w buforze. Komponent UpdateSQL pozwala na wykonanie własnych zapytań SQL, kiedy uaktualnienia, wstawienia lub usunięcia wymaga rekord w zbiorze danych tylko do odczytu. Dla przykładu, przy użyciu komponentu UpdateSQL można określić domyślne wartości dla określonych pól w zbiorze danych.

Właściwość DeleteSQL pozwala zdefiniować zapytanie SQL wywoływane w chwili wprowadzania zmian polegających na usuwaniu rekordów. Analogicznie, InsertSQL umożliwia zdefiniowanie zapytania SQL wykonywanego w chwili, gdy do zbioru danych zostały wstawione nowe rekordy i wprowadzane są zmiany znajdujące się w buforze. Właściwość ModifySQL służy do zdefiniowania zapytania stosowanego w wypadku zmodyfikowania rekordu.

Komponent DataSource

Komponent DataSource udostępnia mechanizmy służące kojarzeniu komponentów reprezentujących zbiory danych (Table, Query lub StoredProc) z komponentami wizualnymi, które wyświetlają dane (DBGrid, DBEdit, DBListBox i inne). Podstawowym celem komponentu DataSource jest ułatwienie wprowadzania zmian w aplikacjach. Wszystkie komponenty danych w formularzu są podłączone do źródła danych (DataSource), które z kolei jest połączone ze zbiorem danych.

Ponieważ komponenty danych nie są związane bezpośrednio ze zbiorem danych, można z łatwością zmienić ten zbiór bez potrzeby ponownego wiązania z nim każdego komponentu w formularzu. Przykładowo, aby zmienić zbiór danych z tabeli (Table) na zapytanie (Query) wystarczy zmodyfikować właściwość DataSet komponentu DataSource i nie ma potrzeby dokonywania jakichkolwiek zmian w pozostałych komponentach.

Klasa TDataSource cechuje się bardzo małą liczbą właściwości. Właściwość Enabled określa, czy komponent danych połączony ze źródłem danych będzie wyświetlał dane; przy wartości False komponent DataSource nie dostarcza danych do połączonych z nim komponentów prezentacyjnych.

Metody klasy TDataSource są większości mało znaczące, dlatego zostaną tutaj pominięte. Zdarzenie OnDataChange jest generowane, gdy po dokonaniu edycji bieżącego rekordu kursor przemieszcza się do innego rekordu. Zdarzenie OnStateChange pojawia się, gdy zmianie ulegnie stan zbioru danych (np. kiedy użytkownik przejdzie z trybu edycji do trybu przeglądania).

Komponent Session

Komponent Session zarządza sesją bazy danych. Za każdym razem, kiedy uruchamiasz aplikację bazy danych, BDE ustawia globalny obiekt klasy TSession o nazwie Session. Można z niego skorzystać, aby uzyskać dostęp do bieżącej sesji bazy danych. Nie musisz tworzyć własnych obiektów klasy TSession, o ile nie tworzysz aplikacji wielowątkowej - zasady wielodostępu do baz BDE narzucają bowiem wymóg, aby każdy wątek uzyskiwał dostęp do określonej bazy danych w ramach odrębnej sesji; w aplikacji jednowątkowej standardowy obiekt klasy TSession jest zazwyczaj wystarczający.

TSession posiada kilka metod godnych szczególnego zainteresowania. Metody AddAlias i AddStandardAlias mogą zostać wykorzystane do stworzenia aliasów BDE w trakcie wykonania programu. Więcej szczegółów na temat tworzenia aliasów BDE znajduje się w sekcji „Tworzenie aliasów BDE”.

Do uzyskania listy aliasów baz danych można wykorzystać metody GetAliasNames i GetDatabaseNames. Są one przydatne, gdy chcemy umożliwić użytkownikom wybór bazy danych z listy. Do tego celu można np. wykorzystać pole listy rozwijalnej:

Session.GetDatabaseNames(DBNameComboBox.Items);

W tym przypadku właściwość Items obiektu combo o nazwie DBNamesComboBox jest wypełniana listą nazw baz danych. Metody GetTableNames i GetStoredProcNames mogą być użyte w ten sam sposób w stosunku do (odpowiednio) nazw tabel i nazw procedur zapamiętanych.

Komponent Database

Komponent Database daje dostęp do specyficznych operacji bazodanowych. W niektórych aplikacjach komponent ten nie jest wymagany, istnieją jednak pewne operacje, które go wymagają. Operacje te są omawiane w kolejnych sekcjach.

Utrzymywanie połączeń z bazą danych

Właściwość KeepConnections kontroluje sposób obsługiwania połączeń z bazą danych w chwili, gdy zamykany jest zbiór danych. Jeżeli wartością KeepConnections jest False, połączenie z bazą zostanie przerwane, gdy zamknięty zostanie ostatni zbiór bazy danych. Wymaga to ponownego logowania przy następnym otwarciu zbioru danych. Pomijając oczywisty fakt, że częste logowanie bywa po prostu irytujące, należy zauważyć, że zajmuje ono trochę czasu - i nie chodzi tu bynajmniej o samo wprowadzanie nazwy użytkownika i hasła, lecz nawiązywanie połączenia i fizyczne logowanie do bazy. Podtrzymanie połączenia nawet po zamknięciu wszystkich zbiorów bazy (w wyniku ustawienia KeepConnections na True) pozwala ten czas zaoszczędzić.

Kontrola logowania

Jednym z powodów stosowania komponentu Database jest kontrola operacji logowania. Logowanie może być nadzorowane na dwa sposoby. Jeden z nich polega na ustawieniu właściwości LoginPrompt na wartość False i jawnym ustawieniu parametrów logowania. Można zrobić to przed otwarciem zbioru danych:

Database1.Params.Values['user name'] := 'SYSDBA';

Database1.Params.Values['password'] := 'masterkey';

Powyższy fragment kodu ustawia nazwę użytkownika i hasło dla bazy danych serwera Local InterBase.

0x01 graphic

Ze względów bezpieczeństwa należy z ostrożnością podchodzić do jawnego umieszczania informacji o haśle we własnych aplikacjach. Nie należy omijać logowania jawnego bez wyraźnych powodów.

Załóżmy dla przykładu, że dysponujesz formularzem, w którym umieszczony zostały komponenty Database i Table. Powiedzmy, że chcesz utworzyć połączenie z bazą danych i otworzyć tabelę bez potrzeby jawnego logowania się. Oto kod wykonujący to zadanie:

Database1.AliasName := 'IBLOCAL';

Database1.DatabaseName := 'BazaDanych';

Database1.Params.Values['user name'] := 'SYSDBA';

Database1.Params.Values['password'] := 'masterkey';

Table1.DatabaseName := 'Database1.DatabaseName';

Table1.TableName := 'CUSTOMER';

Table1.Open;

Na początku tego kodu właściwość Alias komponentu Database przyjmuje wartość IBLOCAL w celu podłączenia do serwera Local InterBase. Następnie właściwości DatabaseName nadawana jest arbitralna wartość - możesz użyć w tym celu dowolnego łańcucha. W kolejnych liniach przychodzi pora na ustawienie parametrów połączenia (nazwy użytkownika i hasła), po czym właściwości DatabaseName komponentu Database przepisywana jest do identyczni nazwanej właściwości komponentu Table. Po określeniu konkretnej tabeli (właściwość TableName) tabela zostaje otwarta.

Inny sposób przeprowadzenia procesu logowania opiera się na zdarzeniu OnLogin. Jest ono generowane za każdym razem, gdy wymagana jest informacja dla procesu logowania. Aby zdarzenie to zostało wygenerowane, właściwość LoginPrompt musi być ustawiona na wartość True. Wystarczy teraz utworzyć odpowiednią procedurę zdarzeniową - mogłaby ona wyglądać następująco:

procedure TForm1.Database1Login(Database : TDatabase;

LoginParams : TStrings);

begin

LoginParams.Values['user name'] := 'SYSDBA';

LoginParams.Values['password'] := 'masterkey';

end;

Czy ten kod nie wydaje się znajomy? Jest zasadniczo ten sam kod, jaki użyty został poprzednio przy bezpośrednim ustawianiu parametrów połączenia z bazą danych. Zazwyczaj nie stosuje się jawnego kodowania nazwy użytkownika i hasła (lub ostatecznie samego hasła), ale za to pobiera się te informacje z zewnętrznego źródła danych, takiego jak komponent edycyjny, plik konfiguracyjny lub Rejestr Windows.

Zarządzanie transakcjami

Innym powodem do stosowania komponentu Database jest zarządzania transakcjami. Standardowo kontrolą transakcji zajmuje się BDE, mogą się jednak zdarzyć sytuacje, kiedy wymagana jest dodatkowa kontrola ze strony użytkownika.

Transakcja to sekwencja uaktualnień przeznaczonych dla zbioru danych. W skład takich uaktualnień mogą wchodzić np. zmiany wprowadzane do rekordów, wstawienia/usuwania rekordów i inne operacje. Istotą transakcji jest to, iż zapoczątkowana musi wykonać się do końca; jakiekolwiek częściowe jej wykonanie nie powinno pozostawić żadnego śladu. Komponent DataBase umożliwia wykonanie tego w następujący sposób:

Przed wykonaniem pierwszej czynności wchodzącej w skład transakcji użytkownik wywołuje metodę StartTransaction. Od tej pory wszystkie operacje wykonywane w związku z bazą danych reprezentowaną przez komponent mają charakter prowizoryczny - ich fizyczne urzeczywistnienie następuje w momencie wywołania metody Commit, która tym samym kończy transakcję. Użytkownik może przerwać transakcję w dowolnej chwili, wywołując metodę RollBack, w wyniku czego dotychczasowe operacje na bazie danych, wchodzące w zakres bieżącej transakcji, zostaną uznane za niebyłe, sama transakcja zaś -anulowana.

Interesującym zagadnieniem jest równoległe wykonywanie kilku transakcji na tej samej bazie danych (ze strony różnych stacji roboczych). Problem nie jest trywialny - czy na przykład zmiany wynikające z nie zatwierdzonych jeszcze operacji jednej transakcji mają być widoczne dla drugiej? O tym i wielu innych aspektach równoległych transakcji decyduje właściwość TransIsolation komponentu DataBase; jej szczegółowy opis znajduje się w systemie pomocy Delphi.

0x01 graphic

Cała sekwencja uaktualnień dokonywanych w ramach transakcji traktowana jest jako niepodzielny ciąg i realizuje się fizycznie w momencie wywołania metody Commit. Jeżeli z jakichś względów nie dojdzie do jej wykonania - bo na przykład wystąpi wyjątek, lub użytkownik anuluje transakcję wywołując metodę RollBack - żadne zmiany zlecone dotychczas w ramach transakcji nie będą widoczne w bazie.

Komponent BatchMove

Komponent BatchMove służy do kopiowania porcji rekordów pomiędzy zbiorami danych. Właściwość Source określa źródłowy zbiór danych, a właściwość Destination - docelowy.

Jeżeli zbiór źródłowy i docelowy nie posiadają identycznej struktury niezbędne jest dokonanie wzajemne przyporządkowanie pól obydwu struktur. Służy do tego właściwość Mapping będąca obiektem klasy TStringList), każdy z łańcuchów określa jedną parę pól - na przykład:

Imie = Im

Nazwisko = Nazw

Uwagi = Koment

Nazwy pól po lewej stronie znaku równości odnoszą się do struktury zbioru docelowego, po prawej zaś - do źródłowego.

0x01 graphic

Zwróć uwagę, że w zapisie odwzorowania stosowany jest pojedynczy znak równości (=), nie zaś pascalowski operator przypisania (:=).

Fizyczne kopiowanie rekordów wykonuje się w ramach metody Execute.

Właściwości komponentu mogą być ustalane zarówno na etapie projektowania, jak i w czasie wykonania programu - na przykład:

DestTable.TableName := 'copy.db'

BatchMove1.Destination := DestTable;

BatchMove1.Source := SourceTable;

BatchMove1.Mode := batCopy;

BatchMove1.Execute;

Właściwość Mode określa sposób, w jaki rekordy są wpisywane do docelowego zbioru danych; znaczenie jej wartości przedstawia tabela 16.6.

Tabela 16.6. Wartości właściwości Mode

Wartość

Opis

BatAppend

Rekordy ze zbioru źródłowego są dołączane na końcu zbioru docelowego.

BatAppendUpdate

Kombinacja wartości batAppend i batUpdate. Jeżeli rekord o danym kluczu już istnieje, zostaje zastąpiony rekordem źródłowym; w przeciwnym wypadku jest dołączany na koniec zbioru. Tabela docelowa musi posiadać indeks, na podstawie którego określa się klucz rekordu.

batCopy

Powoduje utworzenie nowej tabeli i skopiowanie do niej wszystkich rekordów z tabeli źródłowej.

batDelete

Jeżeli w tabeli docelowej znajdą się rekordy posiadające identyczne klucze jak kolejne rekordy z tabeli źródłowej, zostaną one usunięte. Tabela docelowa musi posiadać indeks, na podstawie którego określa się klucz rekordu.

batUpdate

Rekord w tabeli docelowej jest zastępowany rekordem z tabeli źródłowej, jeżeli posiadają one identyczne klucze; tabela docelowa musi posiadać indeks, na podstawie którego określa się klucz rekordu.

0x01 graphic

Zachowaj ostrożność podczas korzystania z trybu batCopy. Wywołanie metody Execute w tym trybie w sytuacji, gdy docelowa tabela już istnieje, spowoduje zniszczenie zawartości tej ostatniej i zastąpienie jej zawartością tabeli źródłowej.

Komponent TField

Klasa TField reprezentuje pole tabeli bazy danych. Dzięki niej możliwe jest ustawienie atrybutów pola, do których zaliczają się: typ danych (łańcuch, wartość całkowita, zmiennoprzecinkowa, itp.), rozmiar pola, indeks, określenie czy pole jest obliczane lub przeglądowe, czy pole jest wymagane itp. Dostęp do wartości pola i jej zmiana możliwe są dzięki właściwościom takim jak AsString, AsVariant, AsInteger itp.

TField jest klasą podstawową dla bardziej specyficznych klas pól. Potomkami klasy TField są: TStringField, TIntegerField, TSmallIntField, TWordField, TFloatField, TCurrencyField, TBCDField, TBooleanField, TDateTimeField, TDateField, TTimeField, TBlobField, TBytesField, TVarBytesField, TMemoField i TGraphicField.

Klasy potomne w niewielkim stopniu rozszerzają funkcjonalność klasy podstawowej. Przykładowo, klasy pól numerycznych posiadają właściwość DisplayFormat określającą sposób wyświetlania liczb i właściwość EditFormat, która determinuje wygląd pola w trakcie jego edycji. Każdy z potomków klasy TField odnosi się do określonego typu pola bazy danych - i tak klasa TIntegerField znajduje zastosowanie w przypadku pól typu całkowitego (integer), klasa TTimeField jest stosowana, gdy typem pola jest data lub czas (lub data/czas), TBlobField jest używana dla pól reprezentujących obiekty binarne (ang. BLOB - Binary Large Object) itp.

Dostęp do właściwości klasy TField na etapie projektowania umożliwia Edytor Pól. Po dodaniu pól wystarczy wybrać dowolne z nich, a jego właściwości wyświetlone zostaną w Inspektorze Obiektów. Rysunek 16.5 przedstawia Edytor Pól oraz Inspektora Obiektów w trakcie przykładowej edycji.

Rysunek 16.5.

Edytor Pól i Inspektor Obiektów

0x01 graphic

Liczba właściwości klasy TField jest na tyle duża, że nie zamierzam przedstawiać tutaj każdej z nich. Zamiast tego przedstawię kilka metod wykorzystania jej samej i jej potomków.

Dostęp do pól

Zanim będzie można pobrać lub ustawić wartość pola, trzeba pole to jednoznacznie określić. Można to zrobić przynajmniej na trzy sposoby:

Prawdopodobnie najrzadziej używaną metodą jest dostęp do pola poprzez nazwę. Ma ona sens tylko wtedy, gdy pola zostały uprzednio dodane do projektu poprzez Edytor Pól. Podczas dodawania pól za pomocą tego edytora Delphi tworzy wskaźnik do każdego z nich korzystając z nazwy tabeli i nazwy pola. Jeżeli - przykładowo - tabela nosi nazwę Table1, a nazwą pola łańcuchowego jest LAST_NAME, Delphi utworzy wskaźnik do obiektu klasy TStringField o nazwie Table1LAST_NAME. Wskaźnika tego można by następnie użyć w celu odwołania się do pola:

Table1LAST_NAME.Value := 'Reisdorph'

Problem związany z takim podejściem polega na tym, że nie zawsze używa się Edytora Pól do określania pól zbioru danych.

Właściwość Fields oferuje inny sposób dostępu do pola - przez pozycję w strukturze. Jeżeli wiemy, że pole LAST_NAME jest pierwszym polem w tabeli, można użyć następującej konstrukcji:

Edit1.Text := Table1.Fields[0].Value;

Oczywiście z tym podejściem jest również związany pewien problem - trzeba dokładnie znać uporządkowanie pól.

Z trzech wymienionych uprzednio sposobów dostępu do pól, najpowszechniej stosowanym i najpewniejszym jest metoda FieldByName. Aby skorzystać z tej metody wystarczy znać jedynie nazwę pola, do którego dostęp chcemy uzyskać:

Table1.FieldByName('LAST_NAME').AsString := Edit1.Text;

FieldByName zwraca wskaźnik typu TField - oto równoważna postać powyższego fragmentu:

var

Field : TField;

begin

Field := Table1.FieldByName('LAST_NAME');

Field.AsString := Edit1.Text;

end;

W większości wypadków metoda FieldByName jest najlepszym rozwiązaniem. Być może zastanawiasz się, który z rekordów zostanie zmodyfikowany w efekcie wykonania powyższego kodu; otóż - wszystkie z przedstawionych technik wyszukują pole w bieżącym rekordzie.

Pobieranie i ustawianie wartości pól

Po uzyskaniu wskaźnika do określonego pola, można modyfikować jego wartość przy użyciu właściwości Value lub dowolnej z właściwości typu As… (AsString, AsInteger itp.). Właściwości te dokonuję konwersji z jednej postaci pola do innej. Oczywiście, nie w każdej sytuacji konwersja taka jest wykonalna - przykładowo, nie da się przekształcić łańcucha „HELION” do liczby całkowitej, próba takiej konwersji skończy się wyjątkiem.

Poniższa sekwencja przypisuje polu LAST_NAME bieżącego rekordu łańcuch pobrany z kontrolki edycyjnej:

Table1.Edit;

Table1.FieldByName('LAST_NAME').AsString := Edit1.Text;

Table1.Post;

Wywołanie metody Edit przełącza tabelę w tryb edycji; jeżeli zapomnisz to zrobić, podczas próby zmodyfikowania wartości pola otrzymasz wyjątek. Po przejściu w tryb edycji następuje przypisanie polu nowej wartości - w tym przypadku zamiast właściwości Value użyta została właściwość AsString. W przypadku pola łańcuchowego nie ma to znaczenia. W efekcie wywołania metody Post dane zapisywane są do zbioru danych (lub do bufora uaktualnień, jeżeli aktywna jest właściwość CachedUpdates) - i na tym kończy się cały proces. Odczytanie wartości pola jest równie proste:

var

AcctNo : Integer;

begin

AcctNo :=Table1.FieldByName('ACCT_NBR').Value;

{ Dalsze instrukcje }

end;

Zdarzenia klasy TField

Zdarzeniami klasy TField wartymi odrębnego omówienia są: OnChange i OnValidate. Zdarzenie OnChange jest generowane za każdym razem, gdy zmienia się wartość pola; ma to miejsce po wysłaniu danych. Zdarzenie to można wykorzystać jeżeli zachodzi potrzeba informowania o każdej zmianie pola.

Zadaniem zdarzenia OnValidate jest weryfikacja poprawności wprowadzonych danych. Jedynym jego parametrem jest wskazanie na przedmiotowy obiekt:

procedure TForm1.Table1ACCT_NBRValidate(Sender : TField);

begin

if Sender.AsInteger < 3000

then

raise EDBEditError.Create('Błędny numer rachunku.');

end;

co jest nieco zaskakujące, gdyż nie istnieje żadne parametr, za pomocą którego można by poinformować o negatywnym wyniku weryfikacji; zgodnie z tym, co sugerować może powyższy przykład, jedynym sposobem takiej sygnalizacji jest wygenerowanie wyjątku.

Aby przypisać w/w zdarzeniom odpowiednie procedury obsługi, należy w Edytorze Pól wybrać żądane z dołączonych pól i posłużyć się Inspektorem Obiektów.

Komponenty baz danych
typu klient/serwer

Razem z Delphi w wersji Client/Server rozpowszechniane są trzy dodatkowe komponenty dostępu do danych, umożliwiające tworzenie wielowarstwowych aplikacji bazodanowych. (Dla przypomnienia - system wielowarstwowy to taki system, w którym aplikacje-klienci komunikują się z jednym lub kilkoma aplikacjami-serwerami (warstwą pośrednią) które z kolei komunikują się z serwerem bazy danych.) Reprezentantami wielowarstwowej architektury bazy danych są komponenty TRemoteServer, TProvider i TClientDataSet.

Komponent TRemoteServer jest stosowany w aplikacji-kliencie do ustanawiania połączenia z jednym lub kilkoma aplikacjami-serwerami. TProvider wykorzystywany jest jako aplikacja-serwer na poziomie pośrednim i pełni rolę kanału między serwerem bazy danych, a aplikacją-klientem. Komponent TClientDataSet jest używany przez aplikację-klienta w celu uzyskania dostępu do dostawcy danych w aplikacji serwerze. Dokładne omówienie możliwości użycia tych obiektów przekracza zakres niniejszej książki.

Tworzenie aliasów BDE

Nie będziesz mógł posunąć się dalej w programowaniu baz danych, jeżeli nie zajmiesz się w końcu tworzeniem aliasów BDE. Przykładowe bazy danych były całkiem dobre, wcześniej czy później będziesz jednak chciał tworzyć aliasy dla własnych baz danych. Kiedy zaczniesz rozpowszechniać swoje aplikacje bazodanowe, będziesz musiał również utworzyć jeden lub kilka aliasów w komputerach użytkowników. Istnieje wiele sposobów tworzenia aliasów:

Aby utworzyć alias, będziesz musiał nakazać użytkownikom uruchomienie Administratora BDE lub samodzielnie stworzyć wszelkie niezbędne aliasy poprzez własny kod. Oczywiście, preferowaną metodą jest samodzielne utworzenie aliasu (nigdy nie lekceważ zdolności użytkowników do „zawalenia” nawet najprostszych zadań). Najpierw przedstawię sposób tworzenia aliasu przy użyciu Administratora BDE, później zajmiemy się tworzeniem aliasu w sposób programowy.

Tworzenie aliasu przy użyciu Administratora BDE

Proces tworzenia aliasu przebiega identycznie dla programów BDE Administrator i SQL Explorer, omówię więc tylko pierwszy z nich.

Załóżmy przez chwilę, że zamierzasz utworzyć aplikację listy adresowej. Pierwszym krokiem, jaki musisz wykonać, jest utworzenie aliasu dla bazy danych. W tym celu:

  1. Uruchom program BDE Administrator (znajdź grupę Delphi w menu Start i wybierz ikonę BDE Administrator). Po uruchomieniu programu w jego oknie wyświetlona zostanie lista aktualnie zainstalowanych aliasów.

  1. Z menu administratora wybierz polecenie Object | New (upewnij się, iż wybraną zakładką jest Database). Wyświetlone zostanie okno dialogowe (New Database Alias), w którym wybrać należy rodzaj sterownika dla nowego aliasu.

  2. Będziesz tworzył bazę danych przy użyciu sterownika standardowego, a ponieważ element STANDARD jest już wybrany wystarczy że klikniesz na przycisku OK. W tej chwili Administrator BDE powinien wyglądać tak, jak przedstawia to rysunek 16.6.

Rysunek 16.6.

Administrator BDE w trakcie tworzenia nowego aliasu bazy danych

0x01 graphic

  1. Administrator BDE oczekuje na wpisanie nazwy aliasu. Wpisz np. MojaBaza i naciśnij Enter.

Teraz musisz wprowadzić kilka dodatkowych informacji w oknie Definition. Pole Type posiada już żądaną wartość STANDARD. W polu DEFAULT DRIVER znajduje się wartość PARADOX i jest to typ sterownika jaki sobie życzysz, więc tutaj również nie trzeba wprowadzać zmian (inne możliwości to dBASE, FOXPRO i ASCIIDRV). Możesz również pozostawić domyślną wartość pola ENABLE BCD. Jedyną informacją, jakiej musisz dostarczyć, jest ścieżka dostępu do miejsca na dysku, gdzie przechowywane będą pliki bazy danych:

  1. Kliknij na polu PATH i wpisz ścieżkę lub użyj przycisku z wielokropkiem aby dotrzeć do odpowiedniego katalogu na dysku.

  1. Zamknij Administrator BDE i potwierdź przyciskiem Yes zapisanie modyfikacji. To wszystko - alias BDE został utworzony.

Wróć do Delphi i umieść w formularzu komponent Table. Sprawdź właściwość Database­Name w Inspektorze Obiektów aby przekonać się czy utworzony przed chwilą alias jest widoczny - jeżeli zrobiłeś wszystko dobrze, powinieneś zobaczyć go wśród innych. W bazie tej nie ma jeszcze żadnych tabel, ale to nic nie szkodzi. Możesz utworzyć je w późniejszym czasie.

Tworzenie aliasów w kodzie programu

Aby uniknąć problemów ze strony użytkowników, lepiej jest tworzyć aliasy wymagane przez program w trakcie jego pierwszego startu. Na szczęście tworzenie aliasów w trakcie pracy programu jest proste. Poniżej znajduje się fragment kodu tworzący alias lokalnej bazy danych Paradox o nazwie LBazaParadox:

CreateDirectory('C:\LPDX', nil);

Session.AddStandardAlias('LBazaParadox','C:\LPDX','');

To wszystko. Naturalnie, należy jeszcze dokonać sprawdzenia, czy katalog i alias zostały utworzone poprawnie, ale w przybliżeniu jest to kod wystarczający.

0x01 graphic

W powyższym przykładzie metoda AddStandardAlias została użyta do utworzenia standardowego (STANDARD) typu aliasu. Aby utworzyć aliasy dla serwerów baz danych innego typu, użyj funkcji AddAlias.

Podsumowanie

Spora dawka wiedzy do przyswojenia. Najlepszy sposób, aby ugruntować materiał zawarty w tym rozdziale, to spędzić trochę czasu na eksperymentach. Weź dowolną przykładową bazę danych i zastosuj filtr w stosunku do jej tabel, popróbuj wyrażeń SQL, poprzeglądaj również bazy danych za pomocą Administratora BDE lub Eksploratora SQL. Na tym etapie nie musisz za wszelką cenę budować kompletnych aplikacji bazodanowych. Po prostu poświęć trochę czasu różnym komponentom i sprawdź, w jaki sposób współpracują ze sobą BDE i bazodanowe komponenty VCL.

Warsztat

Warsztat składa się z pytań kontrolnych oraz ćwiczeń utrwalających i pogłębiających zdobytą wiedzę. Odpowiedzi do pytań możesz znaleźć w dodatku A.

Pytania i odpowiedzi

Nie. Musisz przestrzegać wskazówek zamieszczonych przez firmę Borland w pliku DEPLOY.TXT. Generalnie rzecz biorąc, zainstalowanie dowolnej aplikacji korzystającej z BDE wymaga programu instalacyjnego posiadającego certyfikat firmy Borland.

Zastosowanie komponentu DataSource jako pośrednika ułatwia pracę w sytuacji, gdy zachodzi potrzeba zmiany zbiorów danych. Zamiast zmieniać właściwość DataSet licznych komponentów danych, wystarczy zmienić właściwość DataSet komponentu DataSource. Załóżmy dla przykładu, że zmieniasz zbiór danych z TTable na TQuery (poważna zmiana). Zmiana ta okazałaby się jednak niemal niewidoczna dla komponentów danych, ponieważ całą pracę z tym związaną wykonuje komponent DataSource.

TTable znajduje zastosowanie głównie w przypadku lokalnych baz danych (Paradox lub dBASE), natomiast TQuery - podczas pracy z bazami danych typu klient/serwer. Ostatecznie jednak to do programisty należy decyzja, który z komponentów wykorzystać.

Lokalny serwer InterBase umożliwia tworzenie aplikacji lokalnych baz danych, które w późniejszym czasie z łatwością mogą zostać przekonwertowane do postaci aplikacji typu klient/serwer.

Zazwyczaj nie. Domyślnie obiekt klasy TSession jest tworzony automatycznie dla każdej aplikacji bazy danych. Aby uzyskać dostęp do metod i właściwości klasy TSession możesz skorzystać z obiektu o nazwie Session. Jedyna sytuacja, kiedy wymagane jest tworzenie własnych obiektów TSession, to tworzenie wielowątkowej aplikacji bazodanowej.

Ogólnie mówiąc, nie. Buforowanie uaktualnień jest o wiele ważniejsze dla baz danych typu klient/serwer.

Quiz

  1. Jak można określić lokalną bazę danych?

  1. Co jest celem BDE?

  2. Czy zbiór danych i tabela to jedna i ta sama rzecz? Jeżeli nie, wytłumacz, na czym polega różnica?

  3. Wymień zaletę wynikającą z buforowania uaktualnień.

  4. Czym jest procedura zapamiętana?

  5. Do czego służy właściwość SQL komponentu TQuery?

  6. Wymień powód, dla którego opłacałoby się korzystać z własnego obiektu klasy TDatabase, zamiast z domyślnego obiektu tej klasy?

  7. Z jakiego powodu warto utrzymywać otwarte połączenie z odległą bazą danych nawet wtedy, gdy w danej chwili połączenie to nie jest wykorzystywane?

  8. Do czego służy komponent TBatchMove?

  9. Czym jest alias BDE?

Ćwiczenia

  1. Opisz, w jaki sposób współdziałają ze sobą: aplikacja, BDE i baza danych.

  1. Umieść w formularzu komponenty DataSource, Table i DBGrid. Połącz je ze sobą. Wybierz bazę danych i nazwę tabeli dla komponentu Table. Ustaw właściwość Active tabeli na wartość True. Przejrzyj dane wyświetlone przez komponent DBGrid.

  2. Zmień kilkakrotnie właściwość TableName komponentu Table, wyświetlając w ten sposób zawartość różnych tabel. (Podpowiedź: przed modyfikacją właściwości TableName musisz ustawić właściwość Active na False.)

  3. W formularzu stworzonym w ćwiczeniu drugim umieść komponent Table. Wybierz nazwę bazy danych i nazwę tabeli. Ustaw właściwość Active na True. Teraz zmień właściwość DataSet komponentu DataSource na Table2 (druga tabela). Co stało się z komponentem DBGrid?

  4. Utwórz alias BDE w swoim systemie.

  5. Ćwiczenie dodatkowe: Stwórz tabelę należącą do aliasu BDE stworzonego w ćwiczeniu piątym i wypełnij ją danymi.

Uporządkowany zestaw kolumn tabeli wraz z atrybutami tych kolumn (szerokość, typ danych itp.) nazywany jest często strukturą tabeli (przyp. red.)

Ściślej - tak było w Delphi 1 i 2, gdzie komponent TDataSet zrealizowany był w oparciu o BDE. Począwszy od Delphi 3 możliwe jest tworzenie własnych mechanizmów obsługi baz danych, alternatywnych wobec BDE; podstawowe aspekty funkcjonowania zbioru danych nadal są oferowane przez TDataSet, lecz już na poziomie ogólniejszym, w oderwaniu od konkretnego systemu obsługi (w szczególności BDE). Rolę komponentu-przodka komponentów TTable, TQuery i TStoredProc przejął komponent TBDEDataSet, wywodzący się z TDataSet i stanowiący jego konkretyzację dla właśnie mechanizmu BDE. Nieco mniej istotny jest jeszcze jeden fakt, pominięty przez autora oryginału - bezpośrednim przodkiem komponentów TTable, TQuery i TStoredProc jest (w każdej wersji Delphi) komponent TDBDataSet, wywodzący się z TDataset w Delphi 1 i 2 oraz z TBDEDataSet w Delphi 3 i 4 (por. też rysunki na stronach 1476 i 1477 książki „Delphi 4 Vademecum Profesjonalisty” wyd. HELION 1999). Wydaje się jednak, że opisywana różnica ma subtelny charakter i jako taka pozostaje bez większego znaczenia dla treści niniejszego rozdziału (przyp. red.)

Przy założeniu znajomości języka angielskiego (przyp. tłum.)

Preparacja taka nie jest konieczna - ma ona na celu jedynie optymalizację wykonania procedury (nie zawsze zresztą skuteczną) i nie wpływa na wynik jej wykonania (przyp. red.)

Musi być uruchomiony lokalny serwer InterBase (przyp. red.)

A także na stronach 1431÷ 1432 książki „Delphi 4 Vademecum Profesjonalisty” wyd. HELION, 1999 (przyp. red.)

682 Część III

682 C:\Dokumenty\Roboczy\Delphi 4 dla kazdego\16.doc

C:\Dokumenty\Roboczy\Delphi 4 dla kazdego\16.doc 681

Rozdział 16. Architektura baz danych widziana od strony Delphi 681



Wyszukiwarka

Podobne podstrony:
20, ## Documents ##, Delphi 4 dla każdego
22, ## Documents ##, Delphi 4 dla każdego
07, ## Documents ##, Delphi 4 dla każdego
13, ## Documents ##, Delphi 4 dla każdego
12, ## Documents ##, Delphi 4 dla każdego
19, ## Documents ##, Delphi 4 dla każdego
skoro, ## Documents ##, Delphi 4 dla każdego
Części, ## Documents ##, Delphi 4 dla każdego
11, ## Documents ##, Delphi 4 dla każdego
a, ## Documents ##, Delphi 4 dla każdego
Delphi 4 dla każdego, 01
Delphi 7 dla każdego
B, Informatyka, Delphi 4 dla każdego
Delphi 4 dla każdego, 03
Delphi 4 dla każdego, 04

więcej podobnych podstron