4 28 Programowanie baz danych z użyciem DAO (2)


Rozdział 28.
Programowanie baz danych z użyciem DAO


W tym rozdziale:

Obiektowy model DAO
Klasy DAO w MFC
Tworzenie parametryzowanego rekordsetu DAO
Filtrowanie i sortowanie rekordów z użyciem klasy CDaoRecordset
Wykorzystanie klas MFC DAO do stworzenia narzędziowej aplikacji
DAOSDK
Użycie DAO SDK do pobierania raportów bazy danych Accessa


DAO (Data Access Objects) jest używane przez aplikacje w celu odwołania się do baz danych Accessa. Ponieważ kod obsługujący bazy danych w pakiecie Microsoft Access jest nazywany Jet, terminy Jet i DAO często są używane zamiennie. Oryginalnie DAO zostało opracowane dla pakietu Microsoft Access, a później także dla Visual Basica oraz Visual Basica for Applications. Przez kilka lat oznaczało to, że programiści baz danych chcący odwołać się do Accessa z innych języków mieli właściwie tylko jedną możliwość: ODBC. Choć ODBC (omówione w rozdziale 26.) jest z pewnością potężnym narzędziem dostępu do różnorodnych baz danych, jednak nie jest odpowiednim API dla wszystkich aplikacji baz danych. Jest to szczególnie prawdziwe dla aplikacji baz danych, które chcą odwoływać się jedynie do baz danych Accessa.
Jak wiesz z rozdziału 26., jedną z zalet ODBC w stosunku do innych technologii baz danych jest możliwość zastosowania pojedynczego kodu w celu odwoływania się do różnych baz danych. Niestety, otrzymujemy w wyniku kod stanowiący "najmniejszy wspólny mianownik". Innymi słowy, jeśli piszesz pojedynczy bazowy kod, który ma odwoływać się do różnych baz danych, bez napisania specjalnego kodu dla każdej z nich nie będziesz mógł korzystać ze specyficznych możliwości różnych DBMS-ów. Jednak gdy zaczniesz pisać taki specjalny kod, odejdziesz od głównego założenia, jakim było posiadanie pojedynczego kodu dla wszystkich baz danych. Oprócz tego, pewne funkcje bazy danych Accessa po prostu nie są obsługiwane poprzez sterowniki ODBC dla Accessa.
Microsoft wziął to pod uwagę w DAO w wersji 3.0, udostępniając poprzez Automatyzację (zwaną dawniej Automatyzacją OLE) pełną funkcjonalność engine-a Jet. W wersji 3.0, DAO zostało wydane w formie serwera Automatyzacji in-proc (DLL-a), zawierającego bibliotekę osadzonych typów. Tak jak w przypadku każdej biblioteki typów Automatyzacji aplikacja klienta może odpytywać serwer Automatyzacji w celu uzyskania opisu udostępnionych obiektów (poprzez interfejs OLE IDispatch). Ta właściwość otwiera przed programistami baz danych zupełnie nowe możliwości w odniesieniu do DBMS Accessa. Dzięki DAO 3.0 każdy język obsługujący Automatyzację może mieć dostęp poprzez interfejsy Automatyzacji do praktycznie wszystkich aspektów funkcjonowania engine-a Jet . Tak więc tematem tego rozdziału jest pisanie aplikacji Visual C++ używających DAO w celu odwołania się do bazy danych Accessa.
W tym rozdziale poznasz różne obiekty składające się na hierarchię obiektów DAO. Następnie przejdziemy do różnych interfejsów API DAO, umożliwiających programową manipulację obiektami DAO z wnętrza aplikacji. Po zapoznaniu Cię z tymi interfejsami przedstawimy demonstracyjny program narzędziowy wykorzystujący właśnie klasy DAO MFC. Choć program ma dość niewiele funkcji, jednak doskonale nadaje się jako punkt wyjścia dla większych, bardziej stabilnych aplikacji baz danych korzystających z klas DAO MFC.
Podstawy DAO
Zanim przejdziemy do różnych sposobów programowania interfejsu DAO, ważne jest, byś opanował ogólne podstawy tej technologii oraz jej hierarchię obiektów.
Historia DAO
DAO zostało wprowadzone w Yisual Basic 2.0. W tamtym czasie jeszcze nie miało oficjalnej nazwy, więc wielu programistów nazywało je po prostu obiektami bazy danych. Od tego momentu DAO rozwijało się aż do osiągnięcia obecnej postaci jako serwer in-process Automatyzacji. Oto krótki opis tego rozwoju.
Jet 1.0
Pierwsze wydanie Jet-a. Była to jedna z pierwszych "osobistych" baz danych posiadająca właściwości takie jak aktualizowalne widoki, kwerendy oraz spójny dostęp do niejednorodnych danych.
Jet 1.1
W tej wersji znacznie poprawiono połączenie pomiędzy Jet -em a bazami ODBC oraz zwiększono maksymalny rozmiar bazy danych ze 128 MB do około 1.1 GB. W tej wersji DAO dodano także język definicji danych (DDL, data, definition language). Nowe elementy były dostępne w Yisual Basic 3.0, lecz nie były dostępne w Microsoft Access 1.1.
Jet 2.0
Ta wersja została wprowadzona wraz z pojawieniem się Accessa 2.0. Oferowała większe zmiany niż w przypadku wcześniejszych wersji. Główne poprawki dotyczyły:
Wymuszenia sprawdzania spójności i poprawności danych na poziomie engine'a.
Zoptymalizowania wydajności kwerend poprzez zastosowanie technologii Rushmore uzyskanej od FoxPro.
Zwiększonej zgodności z regułami składni SQL standardu ANSI.
Obsługi UNION, sub-SELECT oraz kwerend definicji danych.
' Pełnego programowego interfejsu dla Jet-a wykorzystującego DAO 2.0
Obsługi kaskadowej aktualizacji i usuwania.
Możliwości przesyłania transakcji (poprzez zdalne zarządzanie transakcjami) do obsługujących je serwerów.
Przekazywanych kwerend SQL-a.
Nowych ustawień MSysConf kontrolujących, jak często i jak wiele danych było pobieranych ze źródeł danych ODBC.
Obsługi zdalnego łączenia indeksów.
Nowych ustawień inicjalizacyjnych dla debuggowania i dostosowywania.
Jak widać, Jet 2.0 wprowadził tyle nowych elementów, że można go uważać za prawie całkiem nową relacyjną bazę danych.
Jet 2.5
Jet 2.5 został opracowany jako wersja pośrednia pomiędzy wersjami 2.0 a 3.0, mająca na celu głównie rozwiązanie problemów związanych z bazami danych niewłaściwie zaznaczanych jako uszkodzone. Jednak oprócz tej poprawki w wersji 2.5 wprowadzono także nowe sterowniki ODBC Accessa, tym razem w wersji 32-bitowej. Także w tej wersji w końcu wprowadzono zmiany, dzięki którym Yisual Basic for Application mógł zacząć korzystać z tych sterowników (VBA[32].DLL).
Jet 3.0
W tej wersji DLL DAO został przeniesiony do serwera Automatyzacji zawierającego bibliotekę osadzonych typów. Jet 3.0 posiadał także nowe możliwości i poprawioną wydajność. Największą i najbardziej oczekiwaną zmianą była w końcu w pełni 32-bitowa implementacja, przeznaczona do użycia w środowiskach takich jak Windows 95 czy Windows NT.
Dokonano wielu zmian w formacie danych oraz sposobie ich obsługi, głównie w celu znacznego przyspieszenia większości operacji. W tym celu skorzystano między innymi z wielowątkowości. Reagując na fakt wykorzystywania Jet -a w dużych systemach baz danych, w Jet 3.0 wprowadzono możliwości replikacji, dzięki czemu programiści mogli tworzyć repliki danych, wykorzystywane w różnych lokalizacjach: Microsoft Jet może automatycznie synchronizować te repliki, zachowując spójność danych.
Jet 3.5
Jedną z głównych zmian w tej wersji jest możliwość replikacji jedynie określonych części tabeli bazy danych, podczas gdy w poprzedniej wersji można było replikować tylko całe tabele. Jet 3.5 posiada także nowy argument Type dla metody openRecordset (): dbOpenForwardOnly. Ten nowy typ rekordsetu działa tak samo jak snapshot DAO 3.0 CdbRecordset Otwarty Z Opcją dbForwardOnly (). Dodatkowo, nowa metoda SetOption () umożliwia wymuszenie już w czasie działania programu własnych ustawień Rejestru dla engine'a Microsoft Jet. Dzięki temu możesz precyzyjnie dostosowywać wydajność kwerend, czasy oczekiwania itd.
Hierarchia DAO
Jak już wspominaliśmy, DAO jest interfejsem API używanym w celu dostępu do baz danych Accessa. Bardziej konkretnie, DAO jest hierarchią obiektów i kolekcji obiektów zawartą w bibliotece DLL. Na szczycie hierarchii znajduje się obiekt DBEngine. Każdy inny obiekt jest podobiektem obiektu DBEngine. Zanim przejdziemy do szczegółowego opisu innych możliwości, spójrz na rysunek 28.1. Przedstawiono na nim pełną hierarchię obiektów DAO dla przestrzeni roboczej Jet (przestrzenie robocze omówimy za moment).
Obiekt DBEngine
Obiekt DBEngine znajduje się na szczycie hierarchii obiektów DAO. W związku z tym ten obiekt zawiera i kontroluje wszystkie inne obiekty w modelu obiektów DAO. Choć wiele innych obiektów DAO posiada powiązane ze sobą kolekcje obiektów, nie jest tak w przypadku obiektu DBEngine. To oznacza, że istnieje jeden i tylko jeden obiekt DBEngine i nie stanowi on elementu żadnej z kolekcji.
Obiekt DBEngine zawiera liczne metody i właściwości, a także dwie kolekcje: works-paces (przestrzenie robocze) oraz Errors (błędy). Obiekt DBEngine jest używany do takich zadań wyższego poziomu jak tworzenie obiektów workspace (przestrzeń robocza) oraz zadań związanych z konserwacją bazy danych, takich jak kompaktowanie i naprawa baz danych Accessa.
Obiekt kolekcji Workspaces
Obiekt kolekcji Workspaces (przestrzenie robocze) stanowi nie uporządkowaną kolekcję wszystkich aktywnych obiektów workspace (przestrzeń robocza). Przestrzeń roboczą możesz traktować jako sesję pomiędzy aplikacją a engine-m Jet -a. Obiekt workspace jest używany do rozpoczynania nowych sesji oraz zarządzania istniejącymi sesjami.
Tak jak w przypadku wszystkich obiektów kolekcji w DAO każdy z obiektów w kolekcji możesz uzyskać albo poprzez nazwę, albo poprzez numer porządkowy. Podobnie jak w przypadku tablic C/C++ indeksy kolekcji DAO liczone są od zera. To oznacza, że w celu odwołania się poprzez indeks do pierwszego obiektu workspace w kolekcji Workspaces musisz podać wartość 0.
DBEngine->

(DBEngine)->Eror

Work spaces(DBEngine)->WorkSpace

Databases(Workspace)->Database
->Containes(Database)->Cortainer ->Documents(Cortainer)->Document
->QueryDefs(Database)->QueryDef(QueryDefs)->Fields(QueryDef)->Field(Eields)
->Parameters(QueryDef)->Parameter(parameters)
->Recordsets(Databese)->Recordset(Recordsets)->Fields(Recorset)->Field(Eields}
->Relations(Databese)->Relation(relations)->Fieds(Relation)->Field(Fields)
->TableDefs(Database)->Table Def(TableDefs)->Fields(TableDef)->Field(Fields)
->Indexses(TableDef)->Indexse(Indekses}
->Fields(Indexe)->Field(Fields)

->Groups(WorkSpace)->Group(Groups)->Users(Group)->User(Users)

->Users(WorkSpace)->User(Users)->Groups(User)->Group(Groups)
KOLEKCJE

DBEngine
Eirors
Work spaces
Databases
Containes
Documents
QueryDefs
Fields
Parameters
Recordsets
Fields
Relations
Fields
TableDefs
Fields
Indexses
Fields
Groups
Users
Users
Groups

OBIEKTY
Eror
WorkSpace
Database
Cortainer
Document
QueryDef
Field
Parameter
Recordset
Field
Relation
Field
Table Def
Field
Indexs
Field
Group
User
User
Group
Obiekt Workspace
Obiekt Workspace (przestrzeń robocza) reprezentuje aktywną, nazwaną sesję z engine'ew Jet-a. Obiektu Workspace możesz użyć do zarządzania transakcjami, tworzenia i otwierania baz danych oraz wykonywania funkcji ochrony, takich jak tworzenie użytkowników i grup.
Zwykle nie musisz sam tworzyć obiektu tego typu, ponieważ domyślny obiekt Workspace jest tworzony automatycznie. Jednak czasem zdarza, się, że aplikacja musi stworzyć unikalne obiekty Workspace - na przykład w celu użycia metod tego obiektu do zarządzania transakcjami (BeginTrans, CommitTrans czy Rollback). Gdy w pojedynczym obiekcie
workspace jest otwartych kilka baz danych, metody transakcji będą odnosić się do tych wszystkich baz danych. Załóżmy, że za pomocą metody openDatabase obiektu workspace otworzyłeś dwie bazy danych. Jeśli wywołasz metodę BeginTrans, a następnie dokonasz modyfikacji w obu bazach danych, po czym wywołasz metodę Rollback, wszystkie zmiany w obu bazach danych zostaną anulowane. Jeśli chcesz, by w obu bazach danych transakcji były zarządzane niezależnie, musisz stworzyć dwa obiekty Workspace, po jednym dla każdej z baz. Do tworzenia unikalnego, nazwanego obiektu Workspace służy metoda CreateWorkspace obiektu DBEngine.
Istnieją dwa typy obiektów workspace: Jet oraz ODBCDirect. W DAO 3.5 Microsoft wprowadził typ przestrzeni roboczej ODBCDirect. Dzięki temu programiści mogą używać DAO do odwoływania się do źródeł danych ODBC bez przechodzenia przez engine Jet. Pokazany wcześniej rysunek 28.1 przedstawia hierarchię obiektów DAO dla przestrzeni roboczej Jet. Ponieważ w tym rozdziale zajmujemy się wyłącznie taką przestrzenią roboczą, więcej informacji na temat użycia ODBCDirect znajdziesz w Microsoft Jet Database Engine Programmer's Guide.
Każdy obiekt workspace należy do kolekcji workspaces, z kolei do każdego obiektu workspace należą trzy inne kolekcje: Databases, Users oraz Groups.
Kolekcja Databases
Kolekcja Databases (bazy danych) zawiera wszystkie otwarte obiekty Database (baza danych) stworzone lub otwarte w nadrzędnym obiekcie workspace. Gdy poprzez obiekt workspace następuje utworzenie lub zmiana bazy danych, powiązany z nią obiekt DAO Database jest dołączany do kolekcji Databases tego obiektu Workspace.
Poza pełnieniem funkcji zwykłego kontenera dla wszystkich otwartych w obiekcie Workspace obiektów Database, kolekcja Databases nie dostarcza zbyt wielu funkcji. Do jedynych użytecznych właściwości i metod kolekcji Databases należą właściwość Count (zawierająca całkowitą liczbę otwartych baz danych) oraz metoda Refresh (używana do odzwierciedlenia schematu bieżącej bazy danych).
Obiekt Database
Obiekt Database (baza danych) reprezentuje otwarte połączenie pomiędzy aplikacją a bazą danych. Fizyczna baza danych może być zarządzana przez DAO tylko poprzez powiązany z nią obiekt Database. Gdy aplikacja utworzy obiekt bazy danych, można nią manipulować poprzez różne właściwości i metody tego obiektu.
Obiekt Database zawiera kilka kolekcji odnoszących się bezpośrednio do schematu bazy danych w fizycznym pliku bazy danych. Na przykład, kolekcje OueryDefs, TableDef s, Relations oraz Containers są zdefiniowane właśnie w obiekcie Database i bezpośrednio odnoszą się do elementów Accessa.
Ważne jest, byś zdawał sobie sprawę, że stworzenie obiektu Database nie oznacza automatycznego nawiązania połączenia z tabelami lub kwerendami powiązanej bazy danych. W tym celu musisz użyć kolekcji TableDefs lub Recordsets. Jednak aby uzyskać dostęp do tabel lub kwerend bazy danych, stworzenie obiektu Database jest konieczne.
Obiekt Database umożliwia zmianę schematu bazy danych poprzez użycie jego metod
CreateQueryDef(), CreateTableDef(), CreateReleation() oraz CreateProperty() . Oprócz tego, metody MakeReplica (), Synchronize () oraz PopulatePartial () służą do tworzenia i synchronizowania pełnych lub częściowych replik pliku bazy danych związanego z obiektem Database.
Kolekcja Recordsets
Kolekcja Recordsets (rekordsety) zawiera wszystkie otwarte obiekty Recordset (re-kordset) dla danego obiektu Database. Jeśli będziesz korzystał z DAO, najczęściej używanym obiektem będzie właśnie Recordset. Podobnie jak kolekcja Databases, kolekcja Recordsets nie udostępnia wielu właściwości i metod poza właściwością Count oraz metodą Ref resh (), udostępnianymi przez wszystkie obiekty kolekcji DAO.
Obiekt Recordset
Obiekt Recordset reprezentuje dane zwrócone w wyniku wykonania kwerendy. Główną różnicą pomiędzy obiektem Recordset a innymi obiektami DAO jest to, że obiekt Recordset nie jest stały. Innymi słowy, o ile inne obiekty DAO, takie jak Database, TableDef czy QueryDef odnoszą się do fizycznych części bazy danych, o tyle obiekt Recordset reprezentuje jedynie zestaw rekordów otrzymanych w wyniku wykonania kwerendy na zestawie jednej lub kilku tabel lub kwerend. Istnieje pięć rodzajów obsługiwanych rekord-setów DAO:
Table - tabela Accessa, którą możesz manipulować przez dodawanie, zmianę i usuwanie rekordów.
Dynaset - dynamiczny zestaw rekordów, którym także można manipulować przez dodawanie, zmianę i usuwanie rekordów.
Snapshot - statyczny zestaw rekordów (nie można go aktualizować).
Forward-Only - identyczny z typemSnapshot, z tym że możesz poruszać się w nim tylko do przodu. (Ten nowo zdefiniowany typ rekordsetu doskonale nadaje się w sytuacjach, w których chcesz przejść przez rekordset tylko raz).
Dynamie - podobny do rekordsetu Dynaset, lecz zjedna ważną różnicą: w tym typie rekordsetu po uruchomieniu kwerendy zwracającej dane, jeśli inny użytkownik doda lub usunie rekordy, które znalazłyby się w tym rekordsecie, zmiany zostaną automatycznie odzwierciedlone w rekordsecie.
Rodzaj rekordsetu jest określany w momencie wywołania metody OpenRecordset (). Domyślnym typem rekordsetu jest tabela Accessa. Ponieważ obiekt Recordset reprezentuje zestaw rekordów otrzymany w wyniku wykonania kwerendy, obiekt tego typu udostępnia również metody do poruszania się po tym zestawie. Należą do nich metody MoveFirst (), MoveNext(), MovePrevious () oraz MoveLast(). Do tworzenia i aktualizowania rekordów służą metody AddNew (), Edit () oraz Update (). Metody Move () oraz Seek () także służą do poruszania się po rekordsecie.
Interfejsy DAO
Mimo iż możliwość porozumienia się z enginem Jet poprzez przemysłowy standard Automatyzacji była szeroko zapowiadanym ulepszeniem, jednak w rzeczywistości programowanie DAO z użyciem Automatyzacji jest powszechnie uważane za raczej trudne zadanie. W odpowiedzi na tę skargę Microsoft opracował inny interfejs dla DAO, zwany DAO SDK. W rzeczywistości interfejs DAO SDK zawiera zestaw klas C++ zwany klasami dbDAO.
Często można spotkać się z terminami DAO SDK i dbDAO używanymi zamiennie. Oprócz DAO SDK Microsoft opracował także zestaw klas DAO przeznaczonych specjalnie dla MFC. Te klasy, zwane klasami MFC DAO, funkcjonalnie są prawie identyczne z klasami MFC używanymi dla źródeł danych ODBC.
Interfejsy Automatyzacji DAO
Ponieważ DAO udostępnia standardową implementację Automatyzacji opartą na tablicach funkcji wirtualnych, DAO SDK zostało opracowane jako pliki nagłówkowe C++ definiujące interfejsy tablic funkcji wirtualnych DAO. To z kolei wyeliminowało potrzebę użycia interfejsu IDispatch. W DAO SDK, gdy aplikacja utworzy egzemplarz obiektu DBEngine, egzemplarze kolejnych obiektów mogą już być tworzone poprzez obiekty nadrzędne, zgodnie z hierarchią DAO przedstawioną na rysunku 28.1. W przeciwnym razie takie obiekty są standardowymi obiektami COM i jako takie muszą zostać jawnie zwolnione, gdy aplikacja przestanie z nich korzystać. Jeśli znasz się na programowaniu z użyciem COM lub Automatyzacji, ten interfejs może Cię zainteresować. Jeśli jednak programujesz w C++ i chciałbyś raczej użyć bardziej przyjaznych interfejsów tego języka, możesz skłonić się ku dwóm innym metodom.
Jeśli mimo to chcesz spróbować swoich sił w programowaniu Automatyzacji, zajrzyj do książki OLE Antomation Programmer's Reference opublikowanej przez Microsoft Press.
Klasy C++ dbDAO
Klasy C++ dbDAO (stanowiące część DAO SDK) stanowiły pierwszą alternatywę dla bezpośredniego korzystania z interfejsu Automatyzacji DAO. Te klasy dawały programiście C++ nie tylko wygodną metodę reprezentacji DAO poprzez klasy C++, ale także udostępniały składnię podobną do języka Visual Basic dla programowania baz danych. Decyzja zastosowania składni podobnej do Visual Basica dla funkcji dbDAO została podjęta z dwóch powodów. Po pierwsze, większość programistów zaznajomionych z bazami danych Accessa znała przynajmniej podstawy programowania Accessa za pomocą Visual Basica. Po drugie, ponieważ w tym czasie stosunek ilości programistów Visual Basica do Yisual C++ wynosił około 5 do l, zdecydowano się na zaspokojenie potrzeb większości. Tak więc, w przypadku dbDAO Microsoft upiekł dwie pieczenie przy jednym ogniu.
Kolejną zaletą dbDAO w stosunku do interfejsu Automatyzacji DAO jest to, że do efektywnego wykorzystania tych klas potrzeba jedynie niewielkiej wiedzy na temat Automatyzacji i COM. Dzieje się tak, ponieważ dbDAO izoluje programistę od szczegółów
związanych z bezpośrednim manipulowaniem interfejsem Automatyzacji DAO. Na przykład, dbDAO automatycznie obsługuje licznik odwołań (używając metod AddRef () oraz Release ()), dynamiczną alokację i dealokację obiektów oraz wiele innych nisko-poziomowych szczegółów Automatyzacji, którymi w przeciwnym razie musiałby się zająć programista.
W skrócie, klasy dbDAO daj ą programiście baz danych te same możliwości co interfejs Automatyzacji DAO, z tym że uwalniają go od konieczności bezpośredniego programowania tego interfejsu.
Klasy MFC DAO
Twórcy MFC opracowali własny interfejs DAO i opublikowali go wraz z Visual C++ 4.0. Klasy MFC DAO posiadają hierarchię podobną do klas baz danych MFC wykorzystujących ODBC. Zaletą tego rozwiązania jest to, że programiści posiadający doświadczenie w korzystaniu z klas baz danych MFC ODBC mogą szybciej opanować klasy baz danych MFC DAO. Oprócz tego, klasy MFC DAO są w pełni zintegrowane z kreatorami MFC oraz hierarchią klas MFC. Należy zauważyć, że mimo iż składnia klas MFC DAO znacznie różni się od składni klas dbDAO, to obie biblioteki klas stanowią po prostu warstwę pośrednią dla interfejsu automatyzacji DAO. Tak więc jeśli czegoś nie da się zrobić poprzez interfejs automatyzacji DAO, można z powodzeniem założyć, że nie powiedzie się to również z użyciem dbDAO lub MFC.
Użycie klas MFC DAO
MFC DAO składa się z OŚmiu klas: CDaoDatabase, CDaoWorkspace, CDaoRecordset, CDaoTableDef, CDaoQueryDef, CDaoException, CDaoRecordview oraz CDaoFieldExchan-ge. Poza specyficznymi dla modelu dokument-widok w MFC klasami CDaoRecordView oraz CDaoFieldEchange pozostałe klasy odnoszą się bezpośrednio do obiektów DAO.
Pod względem hierarchii klas i interfejsu funkcji składowych klasy MFC DAO są zaprojektowane bardzo podobnie do klas baz danych MFC. Jednak poza tym, że klasy baz danych MFC do swojego działania wykorzystują wewnętrznie ODBC SDK (a nie Automatyzację DAO jak klasy MFC DAO), podstawową różnicą jest to, że klasy MFC DAO są dużo bardziej stabilne pod względem funkcjonalności. Na przykład, klasy MFC DAO, w odróżnieniu od klas baz danych MFC, obsługuj ą operacje DDL.
Jak już wspominaliśmy, istnieje tylko osiem klas MFC DAO, mimo istnienia prawie trzydziestu obiektów zdefiniowanych w modelu obiektów DAO (przestrzeni roboczej Jet). Ta ograniczona ilość klas wynika z paru czynników:
4 Hierarchia klas MFC DAO jest "płaską" wersją modelu obiektów DAO. W modelu obiektów DAO, jeśli obiekt może zawierać kolekcję obiektów innego typu, oddzielne obiekty reprezentuj ą zarówno kolekcję, jak i rodzaj zawartych w niej elementów. Na przykład, ponieważ obiekt Database może zawierać wiele obiektów TableDef, w modelu obiektów DAO jest zdefiniowany zarówno
obiekt kolekcji TableDefs, jak i obiekt TableDef. Ponieważ jednak większość obiektów kolekcji DAO posiada jedynie metodę zwracającą ilość obiektów w kolekcji, klasy MFC DAO reprezentują większość kolekcji DAO za pomocą ogólnej klasy kolekcji CMapPtrToPtr.
Klasy MFC DAO nie obsługują pełnej funkcjonalności DAO. Na przykład, klasy MFC DAO nie obsługują żadnych z operacji zabezpieczeń oferowanych przez engine Jet. To oznacza że obiekty DAO Users oraz Groups nie posiadają odpowiadających im klas w MFC. Tak więc w celu zarządzania zabezpieczeniami bazy danych Accessa z poziomu aplikacji C++ musisz skorzystać albo z klas dbDAO, albo z interfejsu Automatyzacji DAO.
Spośród ośmiu klas MFC DAO skupimy się jedynie na klasach wykorzystywanych W "typowej" aplikacji bazy danych: CDaoDatabase, CDaoWorkspace, CDaoRecordset oraz CDaoException. W tym rozdziale nie będziemy zajmować się klasą CDaoRecordView, gdyż stanowi ona jedynie widok z osadzonym w nim obiektem CDaoRecordset. Tak więc, gdy poznasz już klasę CDaoRecordset, wykorzystanie klasy CDaoRecordView nie będzie stanowiło dla Ciebie żadnego problemu.
Klasa CDaoDatabase
Obiekt CDaoDatabase reprezentuje połączenie pomiędzy aplikacją a bazą danych. Po skonstruowaniu obiektu CDaoDatabase w wywołaniu funkcji składowej CDaoDatabase : : open () należy podać nazwę bazy danych. Obiekt CDaoDatabase jest używany zwykle w połączeniu z jednym lub kilkoma obiektami CDaoRecordset (które omówimy za chwilę), lecz może być wykorzystywany również samodzielnie. Przykładem użycia obiektu CDaoDatabase bez korzystania z obiektu CDaoRecordset może być wydawanie takich poleceń SQL dla bazy danych, które nie powodują zwrócenia żadnych danych. Właśnie do tego służy funkcja CDaoDatabase: : Execute (). Oto przykład pokazujący, jak łatwo jest za pomocą klasy CDaoDatabase wstawić rekord do tabeli:
Przykładowa tabela wykorzystywana w tym rozdziale to prosta baza danych Accessa znajdująca się na dołączonej do książki płytce CD-ROM, w kartotece Rozdz26\Database.
try
{
CDaoDatabase db; db.Open("Visual C++ 6 Bibie");
db.Execute("INSERT INTO UserMaster "

"YALUES('TST', 'Testowa nazwa użytkownika', 0)");

db.CloseO ;
} catch(CDaoException* pe)
{
AfxMessageBox(pe->m_pErrorInfo->m_strDescription,
MB_ICONEXCLAMATION); pe->Delete();
}
Gdy do tworzenia aplikacji używasz AppWizarda, jedną z opcji jest możliwość skorzystania lub nieskorzystania z obsługi DAO. Jeśli zdecydujesz się na taką obsługę, do projektu zostanie dołączony odpowiedni pliki nagłówkowe. Jeśli jednak chcesz dodać obsługę DAO do istniejącego projektu, musisz po prostu sam dopisać dyrektywy #include włączające ten plik nagłówkowy (afadao.h) do plików źródłowych projektu.
Przyjrzyjmy się dokładniej poprzedniemu fragmentowi kodu. Widzimy blok try/catch.
try
{
...
}
catch(CDaoException* pe)
{
AfxMessageBox(pe->m_pErrorInfo->m_strDescription,
MB_ICONEXCLAMATION); pe->Delete();
}
Przy korzystaniu z klas MFC DAO wszystkie błędy DAO są zgłaszane jako wyjątki typu CDaoException. Ponieważ w modelu obiektów DAO występuje kolekcja Errors, klasa CDaoException posiada funkcje składowe pobierające informacje od wielu obiektów DAO Error, przechowywanych w kolekcji Errors obiektu DBEngine. Gdy wystąpi błąd DAO, do kolekcji Errors jest dodawany jeden lub kilka obiektów Error. Jeśli jednak do kolekcji ma zostać dodany nowy obiekt Error (lub kilka takich obiektów), najpierw są usuwane poprzednie obiekty. Jeśli operacja DAO nie zwraca żadnego rodzaju błędu, kolekcja Errors zachowuje poprzedni stan. Oto kilka funkcji i zmiennych składowych używanych przy operowaniu obiektami klasy CDaoException:
m_nAfxDaoError - ta zmienna składowa jest ustawiana w momencie wystąpienia błędu DAO, na przykład takiego jak niezainicjalizowanie engin'a Jet.
m_pErrorlnfo - jest to wskaźnik do struktury CDaoErrorinfo zawierającej między innymi zmienną składową zawierającą tekstowy opis błędu.
GetErrorCount() - ta funkcja składowa zwraca ilość obiektów Error w kolekcji Errors obiektu DBEngine.
Ponieważ wszystkie funkcje składowe klas MFC DAO mogą zgłaszać wyjątki typu CDaoException, dobrze jest ująć cały zawierający je kod w blok try/catch wychwytujący wyjątki tej klasy. Pierwszą rzeczą w przykładzie, jaką chcemy zrobić w bloku try, jest skonstruowanie obiektu CDaoDatabase i otwarcie go. Oczywiście, w celu zachowania prostoty nazwa pliku bazy danych została zaszyta w kodzie.
CDaoDatabase db;
if(db.Open("Visual C++ 6 Bibie"))
...
Konstruktor klasy CDaoDatabase nie robi zbyt wiele. Jak już wspominaliśmy, połączenie ze źródłem danych jest tworzone dopiero w momencie wywołania funkcji CDaoDatabase ::Open():
virtual void Open( LPCTSTR IpszName,
BOOL bExclusive = FALSE, BOOL bReadOnly = FALSE, LPCTSTR IpszConnect = _T("") );

throw( CDaoException, CMemoryException );

Jak widać z tego prototypu, aplikacja może przekazać funkcji kilka argumentów, ale wymagany jest tylko pierwszy z nich. We wcześniejszym przykładzie pierwszy argument był jedynym i określał pełną nazwę bazy danych. Pozostałe argumenty są następujące:
Znacznik bExclusive określa, czy mogą być otwierane inne połączenia z bazą danych. Jeśli wskazany plik został już otwarty z tym znacznikiem ustawionym na TRUE, w momencie wywołania funkcji Open() zostanie zgłoszony wyjątek klasy CDaoException.
Znacznik bReadOnly pozwala aplikacji na wskazanie czy podczas połączenia będzie możliwe aktualizowanie danych w bazie. Domyślną wartością tego argumentu jest FALSE. Wartość znacznika automatycznie przejmują wszystkie obiekty CDaoRecordset tworzone i dołączone do tego obiektu CDaoDatabase.
Argument ipszConnect umożliwia aplikacji wskazanie łańcucha połączenia ODBC. Jeśli jednak aplikacja odwołuje się do bazy danych Accessa, ten argument musi być pustym łańcuchem.
Po otwarciu bazy danych następne linie wykonują polecenie SQL i zamykają bazę:
db.Execute("INSERT INTO UserMaster "
"YALUES('TST', 'Testowa nazwa użytkownika', 0)");

db.Close ();

Funkcja CDaoDatabase: :Execute () ma bardzo prostą składnię. Pierwszym (i jedynym wymaganym argumentem) jest łańcuch polecenia SQL lub nazwa kwerendy, jaka ma zostać uruchomiona. Zwróć uwagę, że ta funkcja powinna być używana tylko wtedy, gdy aplikacja przewiduje, że w wyniku danego polecenia lub kwerendy SQL nie zostaną zwrócone żadne dane. W rzeczywistości, wywołanie kwerendy zwracającej dane spowoduje zgłoszenie przez funkcję Execute () wyjątku typu CDaoException.
Drugi argument funkcji Execute () może być przekazany w celu określenia opcji odnoszących się do integracji kwerendy. Na przykład, ten argument może być użyty do włączenia opcji powodującej anulowanie przez bazę danych wszystkich zmian, jeśli podczas wykonania polecenia SQL lub kwerendy wystąpi jakiś błąd.
Klasa CDaoWorkspace
Klasa CDaoWorkspace jest używana do zarządzania nazwanymi sesjami bazy danych. Jak już wspominaliśmy przy okazji omawiania obiektów DAO, aplikacja niekoniecznie wymaga użycia kilku przestrzeni roboczych, ponieważ gdy za pomocą klas MFC DAO jest otwierana baza danych i powiązane z nią rekordsety, tworzona jest także domyślna przestrzeń robocza DAO. Jednak możliwość stosowania kilku przestrzeni roboczych jest przydatna na przykład do logicznego pogrupowania transakcji. Należy pamiętać, że
operacje transakcji (BeginTrans, commit itd.) są wykonywane na poziomie przestrzeni roboczej. Oznacza to, że w celu niezależnego zarządzania transakcjami w obiektach CDaoDatabase konieczne jest jawne utworzenie obiektu CDaoWorkspace dla każdej bazy danych, w której transakcje mają być obsługiwane niezależnie.
Tak więc obiekt CDaoWorkspace utworzony podczas inicjalizacji obiektu DBEngine zapewnia dostęp do domyślnej przestrzeni roboczej. Oprócz tego, w momencie otwarcia lub tworzenia przestrzeni roboczej (lub wywołania którejś z jej statycznych funkcji składowych) jest inicjalizowany obiekt DBEngine, który następnie tworzy dla siebie domyślną przestrzeń roboczą.
Kolejnym użytecznym zestawem funkcji zawartych w klasie CDaoDatabase są funkcje obsługujące transakcje. Transakcje dają aplikacji możliwość łączenia zestawów wywołań funkcji w logiczne grupy. Jeśli wywołanie którejkolwiek z funkcji w grupie logicznej się nie powiedzie, wszystkie zmiany w bazie danych, dokonane przez poprzednio wywoływane funkcje grupy, są anulowane.
Przykładowo załóżmy, że aplikacja księgowa musi przenieść pewną kwotę z jednego konta do innego. Co się stanie, jeśli po odjęciu kwoty z jednego konta nie powiedzie się próba dodania jej do innego konta? Spójność bazy danych zostanie oczywiście naruszona, gdyż pieniądze znikną z jednego konta, ale nie pojawią się na innym. Właśnie w takich przypadkach pomocne stają się transakcje. Dzięki transakcjom, aplikacja może wymagać albo poprawnego wykonania wszystkich funkcji, albo anulowania wykonania każdej z nich. Tak więc transakcję stanowią jeden ze sposobów zapewnienia spójności danych.
Oto jak działają transakcje: wywołanie funkcji CDaoWorkspace: :BeginTrans () powoduje ustawienie "granicy transakcji". Wszelkie zmiany dokonane od tego momentu w bazie danych podlegają ewentualnemu anulowaniu. Gdy zostanie wywołana funkcja CDaoWorkspace: :commitTrans (), zatwierdzane są wszelkie zmiany dokonane w bazie danych od czasu ostatniego ustawienia granicy transakcji. Jeśli z jakiegoś powodu aplikacja uzna, że konieczne jest ręczne anulowanie wszystkich zmian od ustawienia ostatniej granicy transakcji, może to uczynić, wywołując funkcję CDaoWorkspace: :Roilback(). Pamiętaj tylko, że przed próbą wywołania funkcji CDaoWorkspace: :CommitTrans o musisz wcześniej wywołać funkcję CDaoWorkspace : : BeginTrans () .
Klasa CDaoRecordset
O ile obiekt CDaoDatabase reprezentuje połączenie z bazą danych, o tyle obiekt CDaoRecordset reprezentuje zestaw rekordów otrzymanych od jednego z takich połączeń. Jak pamiętasz z wcześniejszej części rozdziału, obiekt DAO Recordset obsługuje pięć różnych rodzajów rekordsetów, jednak obiekt MFC CDaoRecordset obsługuje tylko trzy z nich: table-type, dynaset i snapshot. Rekordset typu table-type reprezentuje pojedynczą tabelę bazową, której dane mogą być modyfikowane przez aplikację. Dynaset to dynamiczny zestaw danych obsługujący dwukierunkowe przewijanie i pozostający w synchronizacji ze zmianami dokonywanymi w bazie danych przez innych użytkowników. Na koniec, snapshot to statyczny obraz danych otrzymany w momencie wypełniania rekordsetu. Ten zestaw danych nie może być aktualizowany.
Najprostszym sposobem zilustrowania wykorzystania rekordsetów jest stworzenie jednego z nich za pomocą ClassWizarda. Użyty rekordset będzie oparty na tabeli UserMaster znajdującej się w bazie danych Accessa, w pliku vc6bib.mdb. Ten plik znajdziesz na dołączonej do książki płytce CD-ROM, w folderze Rozdz26\Database. (Z tego pliku korzystaliśmy także w przykładach w rozdziale 27., w związku z czym następne przykłady i programy demonstracyjne będą bardzo podobne do przykładów z tamtego rozdziału. Dzięki zastosowaniu tej samej tabeli możesz łatwiej dostrzec różnice pomiędzy obiema technologiami). Tabela UserMaster, otwarta w programie Microsoft Access, została przedstawiona na rysunku 28.2.
Aby użyć ClassWizarda, musisz mieć otwarty odpowiedni projekt. Tak więc stwórz taki testowy projekt; możesz go dowolnie nazwać. Gdy już go stworzysz, otwórz ClassWizarda i kliknij przycisk Add Class. W kolejnym menu wybierz polecenie New. Pojawi się okno dialogowe New Class, przedstawione na rysunku 28.3, który pokazuje wartości, jakie powinieneś wpisać w tym oknie.
Gdy nakażesz utworzenie klasy wyprowadzonej z klasy CDaoRecordset, ClassWizard poprosi o wskazanie rodzaju dostępu do bazy danych w nowej klasie rekordsetu. To okno dialogowe (z wybranym plikiem bazy danych) jest pokazane na rysunku 28.4.
Po wskazaniu pliku bazy danych po prostu wskaż tabelę UserMaster (tak jak pokazano na rysunku 28.5) i kliknij przycisk OK. I to już wszystko co jest potrzebne do stworzenia klasy wyprowadzonej z klasy CDaoRecordset!
Po stworzeniu klasy wyprowadzonej z CDaoRecordset przyjrzyj się listingom 28.1 i 28.2, aby zobaczyć, ile pracy zaoszczędził Ci ClassWizard. W tej części rozdziału zajmiemy się więc omówieniem, do czego służą poszczególne zmienne i funkcje składowe.
Listing 28.1. DaoUserMasterSet.h __________________________________
#if _MSC_VER > 1000 ttpragma once
#endif // _MSC_VER > 1000
// DaoUserMasterSet.h : header f ile
//
///////////////////////////////////////////////////////
// CDaoUserMasterSet DAO recordset

class CDaoUserMasterSet : public CDaoRecordset
{
public :
CDaoUserMasterSet (CDaoDatabase* pDatabase = NULL) ;
DECLARE DYNAMIC (CDaoUserMasterSet)
// Field/Param Data
//{{AFX_FIELD(CDaouserMasterset, CDaoRecordset) CString m_sUserID; CString m_sUserName; short m_iStatus; //}}AFX_FIELD

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CDaoUserMasterSet)
public:
virtual CString GetDefaultDBName();
virtual CString GetDefaultSOL();
virtual void DoFieldExchange(CDaoFieldExchange* pFX)
//}}AFX_VIRTUAL

// Implementation ttifdef _DEBUG
virtual void AssertYalid() const;
virtual void Dump(CDumpContext& dc) const; #endif
}
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional
// declarations immediately before the previous linę.
#endif // #defined(AFX_DAOUSERMASTERSET_H_C7818556_097D_11D2_8DB2
// 204C4F4F5020__ INCLUDED_)
Listing 28.2. DaoUserMasterSet.cpp
// DaoUserMasterSet.cpp : implementation file
//
#include"stdafx.h"
#include "DaoUserMaintenance .h"
#include "DaoUserMasterSet .h"

#ifdef _DEBUG
ttdefine new DEBUG_NEW
ttundef THIS_FILE
static char THIS_FILE[] = _ FILE _ ;
#endif
/////////////////////////////////////////////////// // CDaoUserMasterSet
IMPLEMENT_DYNAMIC(CDaoUserMasterSet, CDaoRecordset)
CDaoUserMasterSet::CDaoUserMasterSet(CDaoDatabase* pdb) : CDaoRecordset(pdb)
{
//{{AFX_FIELD_INIT(CDaoUserMasterSet)
m_sUserID = _T("");
m_sUserName = _T("");
m_iStatus = 0;
m_nFields = 3;
//}}AFX_FIELD_INIT
m_nDefaultType = dbOpenSnapshot;
}
CString CDaoUserMasterSet::GetDefaultDBName()
{
return _T("C:\\WINNT\\Profiles\\Administrator\\"
"Desktop\\Books\\Visual C++ 6 Bible\\Rozdz26\V "Database\\vc6bib.mdb");
}
CString CDaoUserMasterSet::GetDefaultSQL()
{
return _T("[UserMaster]"); }
void CDaoUserMasterSet::DoFieldExchange(CDaoFieldExchange* pFX)
}
//{{AFX_FIELD_MAP(CDaoUserMasterSet) pFX->SetFieldType(CDaoFieldExchange::outputColumn); DFX_Text(pFX, _T{"[sUserlD]"), m_sUserID); DFX_Text(pFX, _T("[sUserName]") , m_sUserName); DFX_Short(pFX, _T("[iStatus]"), m_iStatus); //}}AFX FIELD MAP
}
///////////////////////////////////////////////////////////////////////////////////////
// CDaoUserMasterSet diagnostics

#ifdef_DEBUG
void CDaoUserMasterSet::AssertValid() const
{
CDaoRecordset::AssertValid();
}
void CDaoUserMasterSet::Dump(CDumpContext& dc) const {
CDaoRecordset::Dump(dc); } #endif // DEBUG
Gdy już masz klasę wyprowadzoną z CDaoRecordset, aplikacja może użyć jej do następujących celów:
Skonstruowania rekordsetu.
Otwarcia rekordsetu.
Odczytu i zapisu danych z użyciem DFX (DAO field exchange).
4 Filtrowania rekordów.
Sortowania rekordów zawartych w rekordsecie. Nawigacji w rekordsecie. Zapisywania rekordów. Usuwania rekordów.
Konstruowanie rekordsetu
Konstruktor klasy CDaoRecordset wymaga podania tylko jednego parametru, wskaźnika do obiektu CDaoDatabase. Ten argument ma domyślną wartość NULL. Jeśli aplikacja tworząca obiekt CDaoRecordset pozostawi tę wartość jako NULL, MFC automatycznie stworzy tymczasowy obiekt CDaoDatabase używając informacji podanych podczas tworzenia klasy rekordsetu za pomocą ClassWizarda. Te informacje znajdują się wewnątrz funkcji CDaoRecordset : : GetDef aultDBName ( ) .
Jeśli jednak aplikacja ma zamiar korzystać z kilku rekordsetów, dużo efektywniejsze jest stworzenie jednego obiektu CDaoDatabase i użycie go do tworzenia tych rekordsetów. W ten sposób dla wszystkich tworzonych rekordsetów zostanie stworzony tylko jeden obiekt CDaoDatabase zamiast tworzenia takiego obiektu dla każdego rekordsetu. Najlepszym sposobem jest stworzenie obiektu CDaoDatabase podczas inicjalizacji aplikacji i przechowanie wskaźnika do tego obiektu w obiekcie aplikacji lub dokumentu aplikacji. Wtedy, gdy aplikacja zechce skonstruować obiekt CDaoRecordset, będzie mogła skorzystać z tego "globalnego" obiektu bazy danych.
Otwieranie rekordsetu
Po skonstruowaniu rekordsetu do jego otwarcia służy funkcja CDaoRecordset : : Open ( ) . Jednym z argumentów tej funkcji jest typ rekordsetu: dynasetu, snapshotu lub tabeli. Każdy z tych typów rekordsetów jest reprezentowany przez wartość wyliczeniową w klasie CDaoRecordset: dbopenDynaset, dbOpenSnapshot oraz dbOpenTable. Wszystkie te stałe są zdefiniowane w pliku nagłówkowym afxdao.h. Jeśli aplikacja nie określi typu rekordsetu, o typie otwieranego rekordsetu decyduje opcja wskazana w ClassWizardzie podczas tworzenia klasy rekordsetu. Ta wartość jest zawarta w zmiennej składowej m_nDefautType i jest inicjalizowana w konstruktorze klasy. Jeśli aplikacja wywołuje funkcję Open ( ) bez podania łańcucha polecenia SQL, jakie ma zostać wykonane, do wyznaczenia rekordów, jakie mają znaleźć się w rekordsecie po jego otwarciu będzie decydował kod zawarty w funkcji CDaoRecordset : :GetDefaultSQL ( ) .
Odczyt i zapis danych z użyciem DFX
W celu odczytu wszystkich danych z tabeli bez względu na ich kolejność aplikacja może po prostu stworzyć obiekt klasy wyprowadzonej z klasy CDaoRecordset utworzonej przez ClassWizarda i wywołać jego funkcję CDaoRecordset : :0pen ( ) . Jednak w tym momencie nadszedł czas, aby bardziej zagłębić się w zagadnienie przenoszenia danych z bazy do zmiennych w aplikacji. Odpowiedź leży w mechanizmie DFX (DAO field exchange, wymianie pól DAO).
Jeśli klasa wyprowadzona z CDaoRecordset jest tworzona za pomocą ClassWizarda, ClassWizard wylicza wszystkie kolumny w podanej tabeli lub kwerendzie i w pliku nagłówkowym nowej klasy deklaruje zmienne odpowiednich typów. Oprócz tego tworzy funkcję DoFieldExchange (), służącą do powiązania tych zmiennych składowych z odpowiednimi kolumnami bazy danych. W czasie działania programu, gdy MFC chce przenieść dane z bazy do zmiennych lub odwrotnie, wywołuje właśnie funkcję DoFieid-Exchange (). Wszystkie konieczne do tego funkcje DAO (na przykład do powiązania kolumn, pobrania danych itd.) wykonuje kod zawarty w bibliotece MFC. Dzięki temu nie musisz tworzyć własnych, "pomocniczych" funkcji.
Gdy aplikacja otwiera rekordset, funkcja CDaoRecordset: : Open () przechodzi do jego pierwszego rekordu, dzięki czemu można natychmiast zacząć korzystać z danych. Aplikacja może poruszać się po danych, używając funkcji składowych CDaoRecordset: :Move-First(), CDaoRecordset::MoveLast(), CDaoRecordset::MovePrev() oraz CDaoRecordset : :MoveNext (). Omówimy je już za moment.
Filtrowanie rekordów
Gdy już wiesz, jak użyć klas CDaoDatabase i CDaoRecordset do otwarcia i pobrania danych z bazy, następnym krokiem jest nauczenie się filtrowania tych danych w celu otrzymania tylko tych rekordów, na których nam zależy. Można osiągnąć to na kilka sposobów. Jak widzieliśmy w poprzedniej sekcji, "Otwieranie rekordsetu", aplikacja może w funkcji CDaoRecordset: :0pen() określić polecenie SQL dla bazy danych. Oto przykład filtrowania danych za pomocą tej funkcji. Ten fragment kodu odczytuje z tabeli UserMaster tylko te rekordy, których wartość iStatus jest równa zeru:
CDaoDatbase db; CDaoUserMasterSet rs;
try {
rs.Open(AFX_DAO_USE_DEFAULT_TYPE,
"SELECT * from UserMaster where iStatus =O"):
{
while( Irs.IsEOFO ) {
AfxMessageBox(rs.m_sUserName) ;
rs.MoveNext();
}
}
catch(CDaoException* pe) {
AfxMessageBox(pe->m_pErrorInfo->m_strDescription, MB_ICONEXCLAMATION) ;
pe->Delete();
}
Drugim sposobem filtrowania danych zwróconych w rekordsecie jest użycie rekordsetu parametryzowanego. Używając klasy CDaoUserMasterSet (listingi 28.1 i 28.2) stworzonej we wcześniejszej części tego rozdziału, spójrzmy, jakie zmiany są konieczne w kodzie tej klasy, tak aby po wywołaniu funkcji open () zostały pobrane tylko te rekordy tabeli UserMaster, których pole iStatus jest równe zeru.
1. W pliku DaoUserMasterSet.h odszukaj poniższy blok kodu:
// Field/Param Data
//{{AFX_FIELD(CDaoUserMasterSet, CDaoRecordset)
CString m_sUserID;
CString m_sUserName;
short m_iStatus;
//}}AFX_FIELD
2. Poniżej tego bloku dopisz deklarację zmiennej:
int m_iStatusParam;
3. Zainicjuj tę zmienną w konstruktorze rekordsetu (plik DaoUserMasterSet.cpp) i przypisz wartości m_nParams ilość parametrów, które zostaną użyte. (Modyfikowane linie zaznaczono wytłuszczoną czcionką).
//{{AFX_FIELD_INIT(CDaoUserMasterSet)
m_sUserID = _T("");
m_sUserName = _T("");
m_iStatus = 0;
m_nFields = 3;
m_iStatusParam = O;
m_mParams = O;
//}}AFX_FIELD_INIT
m_nDefaultType = dbOpenSnapshot;

To bardzo ważne. Jeśli spróbujesz użyć parametryzowanego rekordsetu bez wcześniejszego ustawienia tej zmiennej, podczas wywołania funkcji DoField-Exchange () funkcja Open () Zgłosi asercję.
4. Zaktualizuj kod funkcji DoFieldExchange (), tak aby wyglądał jak przedstawiony poniżej.
{
void CDaoUserMasterSet::DoFieldExchange(CDaoFieldExchange* pFX)
//{(AFX_FIELD_MAP(CDaoUserMasterSet) pFX->SetFieldType(CDaoFieldExchange::outputColumn); DFX_Text(pFX, _T("[sUserlD]"), m_sUserID); DFX_Text(pFX, _T("[sUserName]"), m_sUserName); DFX_Short(pFX, _T("[iStatus]"), m_iStatus); //}}AFX_ FIELD_ MAP
}
Zwróć uwagę, że funkcja CDaoFieldExchange: :SetFieldType () musi zostać wywołana z argumentem CDaoFieldExchange: : param oraz że drugi argument funkcji DFX_Short () wskazuje kolumnę, według której powinny zostać odfiltro-wane dane (iStatus).
5. Ustaw wartość parametru. Oto przykład odpowiedniego kodu:
CDaoDatabase db; CDaoUserMasterSet rs;
try
{
rs.m_strFilter = "iStatus = ?"; rs.m_iStatusParam = 0; rs.Open();

while(!rs.IsEOF())
{
AfxMessageBox(rs.m_sUserName); rs.MoveNext();
}
}
catch(CDaoException* pe)
{
AfxMessageBox(pe->m_pError!nfo->m_strDescription,
MB_ICONEXCLAMAT!ON); pe->Delete();
}
Jak widać, korzystanie z parametryzowanych rekordsetów nie jest zbyt trudne. Największą przeszkodą jest konieczność ręcznego tworzenia kodu, a jeśli zapomnisz o którymś z kroków, możesz stracić wiele czasu, analizując kod i zastanawiając się, dlaczego re-kordset nie chce poprawnie działać.
Sortowanie rekordów zwróconych przez rekordset
Jak widzieliśmy w sekcji "Filtrowanie rekordów", klasa CDaoRecordset udostępnia zmienną składową (m_strFilter), pozwalającą na filtrowanie rekordów. Klasa CDaoRecordset posiada także inną zmienną składową, przeznaczoną do określania kolejności, w jakiej są sortowane rekordy zwracane podczas otwierania rekordsetu. Ta zmienna nosi nazwę m_strSort. Aby z niej skorzystać, aplikacja musi jedynie przypisać jej nazwę kolumny, według której dane mają być sortowane. Na przykład w poprzednim kodzie, jeśli chciałbyś, by aplikacja posortowała dane w tabeli UserMaster według kolumny iStatus, kod otwierający rekordset powinien wyglądać następująco:
CDaoDatabase db; CDaoUserMasterSet rs; try
{
rs.m_strSort =_T ("iStatus"' rs.Open();
while( Irs.IsEOFO ) {
AfxMessageBox(rs.m_sUserName);
rs.MoveNext();
}
}
catch(CDaoException* pe)
{
AfxMessageBox(pe->m_pError!nfo->m_strDescription,
MB_ICONEXCLAMATION); pe->Delete();
{
Jeśli aplikacja musi sortować dane według więcej niż jednej kolumny, po prostu rozdziel nazwy kolumn przecinkiem.
Poruszanie się po rekordsecie
Teraz gdy wiesz, jak pobierać zestaw odfiltrowanych (poprzez zmienną m_strFilter) i posortowanych rekordów (poprzez zmienną m_strSort), poświęćmy chwilę na omówienie sposobów poruszania się po tym zestawie. Dzięki funkcjom składowym klasy CDaoRecordset, MoveFirst (), MovePrev (), MoveNext () oraz MoveLast (), jest to bardzo łatwe. Z nazw funkcji łatwo można się zorientować do czego służą, jednak wykorzystując je w swoim kodzie, powinieneś pamiętać o kilku rzeczach:
Jeśli aplikacja spróbuje wywołać którąś z tych funkcji dla rekordsetu, który nie zwrócił żadnych rekordów, zostanie zgłoszony wyjątek CDaoException. W związku z tym aplikacja powinna zawsze przed ich wywołaniem wywołać funkcję CDaoRecordset: : IsBOFO lub CDaoRecordset: : IsEOFO .
Funkcje MoveFirst() i MovePrev() nie są obsługiwane przez rekordsety umożliwiające jedynie przewijanie danych w przód. Ta reguła odnosi się jednak tylko dla rekordsetów w przestrzeniach roboczych typu ODBCDirect.
Jeśli aplikacja porusza się po rekordsecie, dla którego istnieje możliwość, że jakiś inny użytkownik usunął rekordy, aplikacja powinna wywołać funkcję CDaoRecordset: : isDeleted () w celu sprawdzenia, czy dany rekord jest wciąż poprawny.
Zapisywanie rekordów
Użycie klasy CDaoRecordset do zapisywania rekordów jest łatwe. Istnieją dwa główne sposoby zapisywania danych: dodawanie nowych rekordów oraz aktualizacja istniejących rekordów.
Aby dodać nowy rekord, aplikacja po prostu musi wykonać poniższe kroki:
1. Wywołać funkcję CDaoRecordset: :AddNew(). Spowoduje to stworzenie nowego, pustego rekordu.
2. Przenieść dane do zmiennych składowych rekordsetu.
3. Wywołać funkcję CDaoRecordset: :Update () w celu zapisania nowego rekordu w bazie danych.
Aby zaktualizować istniejący rekord, aplikacja musi wykonać poniższe kroki: 1. Wywołać funkcję CDaoRecordset: : Edit () .
2. Przenieść nowe dane do zmiennych składowych rekordsetu.
3. Wywołać funkcję CDaoRecordset: : Update () w celu zaktualizowania bieżącego rekordu w źródle danych.
Bardzo ważna jest kolejność wywoływania tych funkcji. Dane muszą zostać przypisane zmiennym składowym już po wywołaniu funkcji Edit (). W przeciwnym razie MFC nie zaktualizuje bazy danych. Powodem jest to, że klasy MFC DAO buforują dane i wykonują porównanie pamięci w momencie wywołania funkcji update (). Jeśli od czasu wywołania funkcji Edit () nie została dokonana żadna zmiana w zmiennych składowych, baza danych nie jest aktualizowana, nawet jeśli aplikacja jawnie wywoła funkcję Update ().
Usuwanie rekordów
Aby usunąć rekord z rekordsetu, dany rekord musi być bieżącym rekordem rekordsetu. W tym momencie aplikacja musi wywołać funkcję CDaoRecordset: :DeleteO. Po usunięciu rekordu konieczne jest wywołanie jednej z funkcji nawigacyjnych rekordsetu w celu przejścia do innego rekordu, gdyż bieżący rekord nie jest już poprawny.
Program demonstrujący użycie MFC DAO
Program demonstracyjny DaoUserMaintenace posiada możliwości tworzenia, aktualizowania i usuwania rekordów, takie jak każda tradycyjna aplikacja narzędziowa. Tak jak w przypadku innych programów demonstracyjnych w tej książce możesz po prostu przeczytać tekst lub jednocześnie spróbować wykonać ćwiczenie. Choć ten program demonstracyjny jest dość prosty, jednak ilustruje, w jaki sposób można wykorzystać klasy MFC DAO do wykonywania podstawowych operacji na bazie danych, takich jak odczyt, zapis i usuwanie rekordów.
Kod źródłowy tej aplikacji znajduje się na dołączonej do książki płytce CD-ROM, w kartotece Rozdz28\DaoUserMaintenance.
Tworzenie demonstracyjnego projektu
Za pomocą AppWizarda stwórz nowy projekt, postępując zgodnie z poniższymi krokami:
1. Stwórz opartą na oknie dialogowym aplikację o nazwie DaoUserMaintenance.
2. Gdy zostaną stworzone pliki projektu, otwórz plik stdafa.h i dodaj do niego dyrektywę włączającą plik nagłówkowy MFC DAO, afodao.h.
Tworzenie interfejsu użytkownika dla programu demonstracyjnego
Po stworzeniu projektu i dołączeniu pliku nagłówkowego MFC DAO musisz zmodyfikować główne okno dialogowe aplikacji. Otwórz wzorzec dialogu IDD_DAOUSERMAINTENANCE_ DIALOG i zmodyfikuj go tak, aby przypominał okno pokazane na rysunku 28.6. (Jeśli skorzystasz z dalszych wskazówek, zaoszczędzisz sobie nieco pracy).
Korzystanie ze schowka w celu powielenia okna dialogowego
Gdy chcesz powielić okno dialogowe znajdujące się w pliku zasobów innego projektu, możesz użyć schowka Windows. Na przykład, ponieważ dialog z rysunku 28.6 jest prawie identyczny z oknem dialogowym z projektu UserMaintenance z rozdziału 27., do skopiowania tego okna możesz użyć schowka. Po prostu otwórz plik UserMaintenance. rc z płytki CD-ROM. Zwróć uwagę, że pojawi się on w widoku, a nie na pasku dialogowym przestrzeni roboczej projektu, gdyż na pasku dialogowym wy stępuj ą jedynie te zasoby, które należą do bieżącego projektu. Gdy odszukasz dialog, z którego chcesz skopiować kontrolki, zaznacz je i skopiuj do schowka. Zapamiętaj rozmiary tego okna dialogowego. Następnie wróć do dialogu IDD_DAOUSERMAINTENANCE_DIALOG i po zmianie jego rozmiaru wklej do niego kontrolki przechowywane w schowku. Dzięki temu nie tylko zaoszczędzisz sobie tworzenia kontrolek, ale także jednocześnie przypiszesz im odpowiednie identyfikatory.
Po skopiowaniu kontrolek stwórz dla każdej z nich zmienną składową, która zostanie użyta w mechanizmie DDX (DAO data exchange). Te zmienne zostały zebrane w tabeli 28. l.
795
Tabela 28.1. Zmienne składowe dla mechanizmu DDX
Kontrolka Kategoria (Category) Typ zmiennej (Variable type) Nazwa zmiennej (Variable name)
Lista Użytkownicy Control CListBox m_lbxUsers
Pole ID użytkownika Value CString m strUserlD
Pole Nazwa użytkownika Value CString m_strUserName
Pole Status Value int m_iStatus
Przycisk Zapisz Control CButton m_btnSave
Przycisk Usuń Control CButton m_btnDelete
Dodawanie klasy pomocniczej reprezentującej dane użytkownika
W celu reprezentowania danych rekordu konieczne będzie stworzenie klasy pomocniczej. Jak zobaczymy za chwilę, po odczytaniu rekordu będzie tworzony obiekt typu cuser. Następnie do listy będzie dopisywany łańcuch (userio) reprezentujący dany rekord. Każda pozycja listy będzie posiadać także wskaźnik do obiektu cuser zawierającego dane użytkownika. W tym momencie stwórz plik o nazwie User. h i wypełnij go poniższym kodem:
#pragma once

class CUser
{
public :
CString m_strUserID;
CString m_strUserName;
int miStatus;
};
Tworzenie klasy rekordsetu dla tabeli UserMaster
Teraz musisz stworzyć klasę rekordsetu potrzebną przy dostępie do tabeli UserMaster. Nazwij ją CDaoUserMasterSet. Jeśli nie pamiętasz, jak to się robiło, przy jej tworzeniu postępuj zgodnie z instrukcjami z poprzedniej sekcji zatytułowanej "Klasa CRecordset".
Modyfikowanie pliku nagłówkowego dialogu
Dla potrzeb programu musisz dokonać kilku niewielkich zmian w pliku nagłówkowym okna dialogowego. Najpierw otwórz plik DaoUserMaintenanceDlg.h i przed deklaracją klasy dialogu umieść poniższą dyrektywę:
#include "User.h"
Następnie dodaj do klasy CDaoUserMaintenanceDlg poniższe deklaracje funkcji składowych:
void FillListboxWithUsers ();
BOOL GetSelectedUser(int* pilndex, CUser** ppUser);
void InitControls(};
BOOL SaveUser(CUser* pUser);
BOOL DeleteUser(CUser* pUser);
Modyfikowanie pliku implementującego dialog
Ostatnią rzeczą, jaką musisz zrobić, jest zmodyfikowanie kodu dialogu w celu odwołania się do bazy danych. Najpierw otwórz plik DaoUserMaintenanceDlg.cpp i dopisz do niego poniższą dyrektywę ttinclude (powinna wystąpić jako ostatnia):
ttinclude "UserMasterSet.h"
Po włączeniu pliku nagłówkowego klasy rekordsetu dodaj poniższą dyrektywę #def ine. Ta wartość zostanie użyta w przypadkach, w których zechcesz użyć obiektu CDaoDatabase bez obiektu CDaoRecordset. Oczywiście, lokalizacja bazy danych jest zaszyta w kodzie, więc jeśli skorzystasz z innej bazy danych, będziesz musiał wskazać jej położenie.
ttdefine DEMO_ DB_ T("\\..\\Rozdz26\\Database\\vc6bib.mdb")

Następnie zlokalizuj funkcję OninitDialogO i umieść na jej końcu, przed instrukcją return, poniższy kod. Funkcja FillListboxWithUsers () wypełnia listę nazwami użytkowników odczytanymi z tabeli userMaster. Funkcja InitControls () inicjuje stan przycisków poleceń (wyłącza przycisk Usuń jeśli na liście nie jest zaznaczony żaden użytkownik, włącza przyciski Zapisz i Usuń, gdy użytkownik jest zaznaczony itd.).
FillListboxWithUsers(); InitControls() ;
Na końcu pliku dopisz poniższą definicję funkcji:
void CDaoUserMaintenanceDlg: :FillListboxWithUsers ()
{
int iIndex; CDaoDatabase db; CDaoUserMasterSet rs;
Ponieważ klasy MFC DAO zgłaszają błędy za pomocą wyjątków, musisz ująć kod związany z bazą danych w blok try/catch.
try
{
Otwórz rekordset i zacznij pętlę while sprawdzającą osiągnięcie końca rekordsetu.
rs.Open();

while (!Irs.IsEOFO ())
{
Zwróć uwagę, że ponieważ nie korzystamy z żadnej z technik filtrowania rekordów omawianych w sekcji "Filtrowanie rekordów", z bazy danych zostaną odczytani wszyscy użytkownicy.
Poniższy kod ustawia daną dla każdego z elementów listy. Dana elementu będzie zawierać wskaźnik do obiektu cuser, dzięki czemu po zaznaczeniu elementu na liście nie trzeba będzie ponownie otwierać bazy danych w celu pobrania informacji.
CUser* pUser = new CUserO; pUser->m_strUserID = rs.m_sUserID; pUser->m_strUserName = rs.m_sUserName; pUser->m_iStatus = rs.m_iStatus;

ilndex = m_lbxUsers.AddString(rs.m_sUserName); m_lbxUsers.SetltemData(ilndex, (DWORD)pUser);

Ten kod tworzy obiekt cuser zawierający wszystkie informacje o użytkowniku odczytane z bazy danych. Następnie użytkownik jest dodawany do listy, a wraz z nim jest zapamiętywany wskaźnik do obiektu cuser, dzięki czemu pobieranie danych jest dużo bardziej efektywne. Zwróć uwagę, że przez zwykłe wywołanie funkcji Open () i MoveNext () rekordsetu jego zmienne składowe są automatycznie wypełnianie informacjami o bieżącym rekordzie. W tym momencie wywołujemy funkcję MoveNext () w celu przejścia do następnego rekordu w rekordsecie:
rs.MoveNext();
}
}
catch(CDaoException* pe) {
AfxMessageBox(pe->m_pError!nfo->m_strDescription, MB ICONEKCLAMATION );
}
}
Na końcu pliku DaolIserMaintenanceDlg.cpp dopisz funkcję inicjalizującą:
void CDaoUserMaintenanceDlg::InitControls()
{
Zainicjuj kontrolki, czyszcząc zawartość wszystkich pól edycji i wyłączając przyciski Zapisz i Usuń. Te przyciski zostaną włączone dopiero wtedy, gdy użytkownik wybierze jakiś element listy.
m_lbxUsers.SetCurSel(-1) ;

m_strUserID = ""; m_strUserName = "" ; m_iStatus = -1;

m_btnSave.EnableWindow(FALSE); m_btnDelete.EnableWindow(FALSE);

m_lbxUsers.SetFocus() ;
UpdateData(FALSE);
Teraz musisz dodać funkcję, dzięki której po wybraniu użytkownika na liście w polach tekstowych pojawią się odpowiednie informacje. W tym celu za pomocą ClassWizarda zaimplementuj funkcję obsługi komunikatu LBN_SELCHANGE dla kontrolki listy (IDC_LBX_USERS). Następnie wypełnij ją poniższym kodem:
void CDaoUserMaintenanceDlg::OnSelchangeLbxUsers() {
int ilndex;
CUser* pUser;

if (GetSelectedUser(&ilndex, SpUser))
{
Zaktualizuj zmienne składowe informacjami o aktualnie zaznaczonym użytkowniku:
m_strUserID = pUser->m_strUserID; m_strUserName = pUser->m_strUserName; m_iStatus = pUser->m_iStatus;
Ponieważ użytkownik jest wybrany, możemy włączyć przyciski Zapisz i Usuń.
m_btnSave.EnableWindow(TRUE) ; m_btnDelete.EnableWindow(TRUE) ;
Wywołanie funkcji UpdateData () z argumentem FALSE spowoduje, że DDX (dialog data exchange) zaktualizuje w oknie dialogowym zgodnie z zawartością zmiennych składowych.
UpdateData(FALSE);
}
}
Ponieważ mamy zamiar często pobierać informacje o wskazanym użytkowniku (w momencie kliknięcia na przycisku Zapisz, przycisku Usuń itd.), napiszmy pomocniczą funkcję wykonującą to zadanie.
BOOL CDaoUserMaintenanceDlg::GetSelectedUser( int* pilndex, CUser** ppUser) {
BOOL bSuccess = FALSE;

int ilndex;
if (LB_ERR != (iIndex =m_lbxUsers.GetCurSel()))
{
*pilndex =iIndex;
*ppUser = (CUser*)m_lbxUsers.GetItemData(iIndex) ; bSuccess = TRUE;
}
return bSuccess;
}
W tym momencie aplikacja posiada już funkcje potrzebne do odczytania wszystkich użytkowników z tabeli UserMaster i wyświetlenia w oknie dialogowym odpowiednich informacji o poszczególnych użytkownikach. Nadszedł więc czas, aby dodać możliwość "aktualizacji" bazy danych. Dodaj funkcję obsługi komunikatu BN_CLICKED dla przycisku Zapisz (IDC_BTN_SAVE) i wypełnij ją poniższym kodem:
void CDaoUserMaintenanceDlg::OnBtnSave()
{ intiCurrIndex;

CUser* pUser = NULL;
if (GetSelectedUser(&iCurr!ndex, spUser))
{
ASSERT(pUser);
if (pUser)
{ UpdateData();
Dodaj poniższy kod w celu zapisania danych wpisanych przez użytkownika. W ten sposób, gdy wywołanie funkcji saveUser () się nie powiedzie, dane nie zostaną utracone i będzie można podjąć kolejną próbę zapisu bez konieczności ponownego wprowadzania informacji przez użytkownika.
CString strPrevUserID; strPrevUserID = m_strUserID; CString strPrevUserName; strPrevUserName = m_strUserName; int iPrevStatus = m_iStatus;

pUser->m_strUserID = m_strUserID; pUser->m_strUserName = m_strUserName; pUser->m_iStatus = m_iStatus;
Następnie wywołaj funkcję saveUser () w celu zapisania bieżącego użytkownika.
if (SaveUser(pUser))
{
Ponieważ w kontrolce listy jest używana nazwa użytkownika, która może się zmienić, należy usunąć jaz listy i dopisać ponownie, tak jak pokazano na listingu 28.3.
Listing 28.3. Ponownie dopisywanie użytkownika do listy
if (LB_ERR == m_lbxUsers.DeleteString(iCurr!ndex)) {
AfxMessageBox("ID użytkownika zostało zapisane, " "ale nie powiodło się usunięcie z listy poprzedniego " "ID użytkownika."); }
else { int iNewIndex =m_lbxUsers.AddString(
pUser->m_strUserName);
if ((LB_ERR == iNewIndex) |l (LB_ERRSPACE == iNewIndex))
{
AfxMessageBox("ID użytkownika zostało zapisane, "ale nie powiodło się dodanie do listy nowego "ID użytkownika.");
}
if (LB_ERR == m_lbxUsers.SetItemData(iNew!ndex,
(DWORD)pUser))
{
AfxMessageBox("SetltemData zwróciło LB_ERR. " "Prawdopodobnie spowoduje to poważne problemy " "jeśli spróbujesz zaktualizować lub usunąć" "ten element z listy.");
}
}
InitControls ( ) ;

UpdateData (FALSE) ; } else
{
Oczywiście, możemy sprawdzić, czy nastąpiła zmiana nazwy użytkownika, jednak nasz sposób jest prostszy i nie wprowadza praktycznie żadnego narzutu, jeśli chodzi o wydajność działania programu. Następnie wpisz poniższy kod zapewniający że gdy wywołanie funkcji saveUser ( ) się nie powiedzie, zostaną odtworzone wartości kontrolek w dialogu:
pUser->m_strUserID = strPrevUserID; pUser->m_strUserName = strPrevUserName; pUser->m_iStatus = iPrevStatus; AfxMessageBox ( "Funkcja SaveUser się nie powiodła");
}
}
}
else {
// Nie powinno nas tu nigdy być, z powodu
// włączania/wyłączania przycisku na podstawie
// elementu zaznaczonego na liście.
AfxMessageBox("Musisz najpierw wybrać ID użytkownika
"przeznaczonego do zapisania");
}
}
Dodaj poniższą funkcję pomocniczą, wywoływaną, gdy wystąpi konieczność zapisania informacji znajdujących się w oknie dialogowym.
BOOL CDaoUserMaintenanceDlg::SaveUser(CUser* pUser)
{
BOOL bSuccess = FALSE; CDaoDatabase db;

try
{ db.Open(DEMO DB);

Obiektu CDaoDatabase możesz także użyć samodzielnie (zamiast obiektu CDaoRecord-set). W przypadku gdy masz zamiar wydać bazie danych polecenie SQL (takie jak poniższe polecenie SQL UPDATE), lecz nie masz bieżącego rekordu, bardziej efektywne jest użycie funkcji CDaoDatabase: :Execute(). Listing 28.4 przedstawia sposób budowy polecenia SQL i przekazania go funkcji Execute () w celu przetworzenia.
Listing 28.4. Budowanie polecenia SOŁ i przekazywanie go funkcji Execute()
CString strSQL = CString("UPDATE UserMaster SET "}; strSQL += CString("sUserName = '") + pUser->m_strUserName + CString("', ");

strSQL += CString("iStatus = " ) ; char szStatus[10];
itoa(pUser->m_iStatus, szStatus, 10); strSQL += szStatus;

strSQL +=CString("WHERE sUserlD = ");
strSQL +=CString{"'") + pUser->mstrUserID + CString ("'");
db.Execute(strSQL);
bSuccess = TRUE;
} catch(CDaoException* pe)
{
AfxMessageBox(pe->m_pError!nfo->m_strDescription, MB_ICONEXCLAMATION ) ;

pe->Delete();
} return bSuccess;
}
Na koniec spróbujemy za pomocą DAO usunąć rekord. W tym celu dodaj funkcję obsługi komunikatu BN_CLICKED dla przycisku Usuń (IDC_BTN_DELETE) i wypełnij ją kodem z listingu 28.5.
Listing 28.5. Funkcja obsługi komunikatu BN^CLICKED dla przycisku Usuń________
void CDaoUserMaintenanceDlg::OnBtnDelete() {
int ilndex; CUser* pUser = NULL;
if (GetSelectedUser(&ilndex, &pUser)) {
ASSERT(pUser); if (pUser) {
UpdateData(); CString strMsg;
strMsg = "Czy jesteś pewien że chcesz usunąć "
"użytkownika "; strMsg += "'";
strMsg += pUser->m_strUserName; strMsg += "'?"; if (IDYES == AfxMessageBox{strMsg, MB_YESNOCANCEL))
{
if (DeleteUser(pUser))
{ if (LB_ERR == m_lbxUsers.DeleteString(ilndex))
{
AfxMessageBox{"Usunięcie użytkownika się powiodło, "
"lecz nie udało się usunąć go z listy."); }
InitControls ();
UpdateData(FALSE);
} else
{ AfxMessageBox("Funkcja DeleteUser się nie powiodła");
}
}
}
}
else {
// Nie powinno nas tu nigdy być, z powodu
// włączania/wyłączania przycisku na podstawie
// elementu zaznaczonego na liście.
AfxMessageBox("Musisz najpierw wybrać ID użytkownika
"przeznaczonego do usunięcia");
}
}
Na końcu pliku dopisz poniższą funkcję pomocniczą, wywoływaną w momencie wystąpienia konieczności usunięcia rekordu:
BOOL CDaoUserMaintenanceDlg::DeleteUser(CUser* pUser) {
BOOL bSuccess = FALSE;
CDaoDatabase db;

try
{
Także tym razem nazwa bazy danych jest zaszyta w poniższym kodzie. Oczywiście, w rzeczywistej aplikacji użylibyśmy w tym celu wskaźnika do obiektu bazy danych lub przynajmniej stałej wartości zdefiniowanej w jednym miejscu pliku.
db.Open(DEMO_DB);

CString strSQL;
strSQL.Format("DELETE FROM UserMaster WHERE " "sUserlD = '%s'", pUser->m_strUserID);
db.Execute(strSQL);

bSuccess = TRUE; } catch(CDaoException* pe)
{
AfxMessageBox(pe->m_pError!nfo->m_strDescription, MB ICONEKCLAMATION );

pe->Delete();
}
return bSuccess; }
Po dodaniu obsługi usuwania rekordów dodamy jeszcze jedną funkcję służącą do zwalniania pamięci zaalokowanej w momencie tworzenia obiektów cuser. W tym celu możemy dodać funkcję obsługi komunikatu WM_DESTROY i wypełnić ją poniższym kodem:
void CDaoUserMaintenanceDlg::OnDestroy()
{ CDialog : :OnDestroy ();

CUser* pUser;

for (int i = 0; i < m lbxUsers.GetCount(); i++)
{
pUser = (CUser*)m_lbxUsers.GetltemData(i); if (pUser)
{
delete pUser;
}
}
}
Testowanie aplikacji DaoUserMaintenance
Po zbudowaniu i uruchomieniu aplikacji powinieneś ujrzeć okno podobne do pokazanego na rysunku 28.7.
Podsumowanie
W tym rozdziale poznałeś podstawy DAO, różne obiekty składające się na hierarchię obiektów DAO oraz różne interfejsy API DAO pozwalające na programowanie manipulowanie engine'em Jet z poziomu aplikacji. Oprócz tego, nauczyłeś się korzystać z klas MFC DAO do tworzenia narzędziowej aplikacji bazy danych. Chociaż nie twierdzimy, że uzyskałeś przy tym większą kontrolę nad danymi niż w przypadku ODBC SDK, jednak pojedyncze, jednolite API bazy danych, takie jak DAO, często może być bardzo przydatne.
Praca z bibliotekami DLL
Uzupełnianie programów
o wyświetlanie obrazków

Wyszukiwarka

Podobne podstrony:
4 Programowanie baz danych (2)
Wprowadzenie do baz danych
Podstawy baz danych zajecia 2 z SQL Tabela Biblioteka
2004 11 Porównanie serwerów relacyjnych baz danych Open Source [Bazy Danych]
IWZ 2 Podstawy baz danych
Analiza baz danych na temat materiałów betonopodobnych
wprowadzenie do baz danych
system baz danych
Administrator baz danych!3101
22 Część VII Udostępnianie baz danych w sieci WWW Podsta
18 Część VI Przegląd baz danych Oracle

więcej podobnych podstron