Nachdem Sie einen Eindruck von der Arbeitsweise einer Anwendung mit einer ODBC-Datenbank - einer der ältesten Technologien des Datenbankzugriffs von Microsoft - gewonnen haben, wenden wir uns nun der neuesten Datenbanktechnologie von Microsoft zu, den ActiveX Data Objects (ADO). ADO ist für alle Programmier- und Skriptingwerkzeuge von Microsoft vorgesehen und bietet dem Visual C++-Programmierer neue Möglichkeiten bei der Datenbankprogrammierung, wobei gleichzeitig die Funktionalität vertraut bleibt. Heute lernen Sie, wie ...
Microsoft hat vor einigen Jahren OLE DB als neue Technologie des Datenbankzugriffs auf den Markt gebracht. Diese Technologie zielte nicht nur darauf ab, Daten einfach mit Datenbanken auszutauschen, sondern sollte auch den Zugriff auf Daten ermöglichen, die an einer beliebigen Stelle gespeichert sind. Über die Technologie OLE DB konnte man auf Mail-Nachrichten, Tabellenblätter, Dateien usw. zugreifen - kurz gesagt, auf alles, was in irgendeiner Form mit Daten zu tun hatte. Diese Technologie war eine der ersten, die zur Forschung und Entwicklung des objektorientierten Dateisystems gehörte, das bei Microsoft in den vergangenen Jahren unter der Bezeichnung »Cairo« firmierte.
Wie man sich gut vorstellen kann, ist es bei der umfangreichen Funktionalität, die OLE DB für den Zugriff auf Daten in den unterschiedlichsten Quellen braucht, ziemlich kompliziert, mit dieser Technologie zu arbeiten. An dieser Stelle kommen die ActiveX Data Objects ins Spiel. Das Konzept von ADO stellt praktisch eine zusätzliche Schicht über OLE DB dar, die speziell für den Datenbankzugriff vorgesehen ist.
Eines der Ziele bei der Entwicklung von ADO bestand darin, ein Steuerelement zu schaffen, das den Datenzugriff und die Steuerung in Webseiten gestattet, wobei die Datensätze auf dem Client zwischengespeichert werden. Zum Teil begründet sich dieses Ziel damit, daß der Benutzer eines Webbrowsers auf eine ganze Gruppe von Datensätzen zugreifen kann, ohne die einzelnen Datensätze nacheinander herunterladen zu müssen, um durch die Datensätze navigieren und Änderungen daran vornehmen zu können. Aus diesen Gründen gehört das ADO-Steuerelement zum Lieferumfang des Webbrowsers Internet Explorer von Microsoft (ab der Version 4.0 aufwärts).
Um den Einsatz von ADO in Skriptsprachen wie VBScript genauso einfach zu gestalten wie in Programmierumgebungen wie Visual Basic, hat Microsoft versucht, die Anzahl der Objekte auf einem Minimum zu halten. Im Ergebnis haben Sie es mit einer kleinen Anzahl von Basisobjekten zu tun:
Connection
Error
Command
Parameter
Recordset
Field
Daneben existieren noch Sammelobjekte für die Aufnahme von Kollektionen der Objekte
Error
, Parameter
und Field
.
Das Objekt Connection
dient dazu, eine Verbindung zu einer Datenbank einzurichten
und zu verwalten. Man konfiguriert das Objekt mit der Verbindungsinformation, einschließlich
dem Standort der Datenbank, der Benutzer-ID und dem Kennwort, bevor
man die Verbindung öffnet. Wenn alle Angaben ordnungsgemäß konfiguriert sind,
sollte das Verbindungsobjekt seine Open
-Methode aufrufen, um die Verbindung zu öffnen.
Sobald das Connection
-Objekt seinen Gültigkeitsbereich verliert, wird die Verbindung
automatisch geschlossen. Wenn Sie weitergehenden Einfluß auf das Schließen
und Öffnen der Datenbankverbindung nehmen möchten, können Sie auch die Close
-
Methode des Objekts Connection
aufrufen, um die Verbindung zu schließen.
Über das Connection
-Objekt wird auch die gesamte Funktionalität der höheren Ebenen
gesteuert. Dazu gehört die Kontrolle der Transaktionsverarbeitung mit den Methoden
BeginTrans
, CommitTrans
und RollbackTrans
des Connection
-Objekts.
Bei jedem Datenbankfehler werden die Fehlerinformationen aus der Datenbank in ein
ADO Error
-Objekt geschrieben. Bei den Fehlerinformationen im Error
-Objekt handelt
es sich um die Fehlerinformationen aus der Datenbank und nicht um Fehlerinformationen
von ADO. Wenn Sie einen Fehler erhalten und anhand der Fehlerinformationen
die Ursachen ermitteln wollen, müssen Sie die Fehlercodes der Datenbank und
nicht die von ADO prüfen.
Über das Objekt Command
werden die Befehle in der Datenbank ausgeführt. Mit diesem
Objekt lassen sich SQL-Anweisungen ausführen oder gespeicherte Prozeduren (SQL-
Funktionen, die in der Datenbank gespeichert sind) aufrufen. Bei jedem Befehl, der
Datenzeilen zurückgibt, müssen Sie das Command
-Objekt mit einem Recordset
-Objekt
verbinden, in dem sich die zurückgegebenen Daten ablegen lassen.
Beim Aufruf einer gespeicherten Prozedur sind oftmals - wie bei Funktionen in anderen
Programmiersprachen - Parameter zu übergeben. Dazu weist man dem Command
-
Objekt eine Reihe von Parameter
-Objekten zu. Jedes Parameter
-Objekt verfügt über
den Namen des Parameters, für den es den Wert aufnimmt, sowie den Wert, der an
die Datenbank für den betreffenden Parameter zu übergeben ist.
Mit dem Parameter
-Objekt lassen sich Variablen für den Aufruf gespeicherter Prozeduren
oder parametrisierter Abfragen übergeben. Diese werden an ein Command
-Objekt
gebunden und beim Aufruf des Befehls verwendet, der im Command
-Objekt programmiert
wurde.
Das Recordset
-Objekt enthält eine Gruppe von Datensätzen aus der Datenbank. Die
Menge der Datensätze ist das Ergebnis eines Befehls, der an die Datenbank gesendet
wurde und die Rückgabe einer Gruppe von Datensätzen bewirkt. Durch den Recordset
können Sie genauso navigieren, wie es bei Recordset
-Objekten in anderen Technologien
des Datenbankzugriffs üblich ist. Auf die einzelnen Felder eines Datensatzes
im Recordset kann man über die Field
-Objekte zugreifen, die mit dem Recordset verbunden
sind. Man kann die Datensätze im Recordset
aktualisieren und dann mit dem
Recordset
die Datenbank auf den neuesten Stand bringen. Es lassen sich auch neue
Datensätze in den Recordset
einfügen oder daraus löschen. Diese Änderungen kann
man dann an die Datenbank übertragen.
Das Field
-Objekt repräsentiert eine einzelne Spalte im Recordset
. Jedes Field
-Objekt
enthält den Spaltennamen, den Datenwert und wie der Datenwert darzustellen ist.
Da ADO für den Einsatz in den Skriptsprachen von Microsoft vorgesehen ist und in
diesen Skriptsprachen lediglich der Datentyp Variant
zur Verfügung steht, enthalten
Field
-Objekte immer einen Datenwert vom Typ Variant
. Der Datenwert wird automatisch
in den jeweiligen Datentyp konvertiert, wenn die Datenbank aktualisiert wird.
Als Programmierer, der mit den ADO-Objekten arbeitet, müssen Sie den Wert vom
Datentyp Variant
in den benötigten Datentyp umwandeln und beim Aktualisieren des
Wertes diese Umwandlung in der anderen Richtung vornehmen.
In Ihren Visual C++-Anwendungen können Sie das ADO-Steuerelement nach zwei verschiedenen Methoden einsetzen. Der einfache Weg zum Einbinden von ADO in Ihre Anwendung verläuft über ActiveX-Steuerelemente. Das ADO-Datensteuerelement können Sie in Ihr Visual C++-Projekt genau wie jedes andere ActiveX-Steuerelement aufnehmen (siehe Abbildung 15.1).
Abbildung 15.1:
Das ActiveX-Steuerelement ADO in ein Projekt aufnehmen
Nachdem Sie das ADO-Steuerelement in Ihr Projekt eingebunden und in einem Fenster plaziert haben, müssen Sie die Datenverbindung in den Steuerelementeigenschaften festlegen, wie es Abbildung 15.2 zeigt. Außerdem ist die Quelle für die Datensätze anzugeben, die das Steuerelement abruft (siehe Abbildung 15.3).
Abbildung 15.2:
Die Datenbankverbindung festlegen
Abbildung 15.3:
Die Datenquelle (Record Source) festlegen
Um das ADO-Steuerelement effizient nutzen zu können, wird man auch datengebundene Steuerelemente, die ADO-fähig sind, einsetzen, beispielsweise das Steuerelement DataGrid von Microsoft. Wenn man diese Steuerelemente in das Fenster mit dem ADO-Steuerelement aufnimmt, legt man das ADO-Steuerelement als Datenquelle für das Steuerelement fest, wie es Abbildung 15.4 zeigt. Wenn das Steuerelement lediglich den Zugriff für ein einzelnes Feld in einem Recordset bereitstellen soll, muß man auch festlegen, welches Feld für das Steuerelement zu verwenden ist.
Abbildung 15.4:
Die Datenquelle (Data Source) festlegen
Nachdem Sie alle derartigen Steuerelemente in das Fenster aufgenommen und konfiguriert haben, bietet Ihre Anwendung vollen Datenbankzugriff über ADO, ohne daß Sie dafür eine einzige Codezeile schreiben mußten (siehe Abbildung 15.5).
Abbildung 15.5:
Eine laufende Anwendung mit dem ADO-Steuerelement
Datenbankanwendungen lassen sich also sehr einfach erstellen: Man plaziert Steuerelemente in einem Fenster und konfiguriert deren Eigenschaften, um die Datenpfade zu beschreiben. Wo liegt nun der Nachteil, wenn man ADO-Anwendungen auf diese Weise erstellt? Erstens bringt diese Lösung eine Menge unnötigen Verwaltungsaufwand mit sich. Für jede SQL-Abfrage oder Tabelle, die man in einen separaten Recordset stellen möchte, ist ein eigenes ADO-Steuerelement erforderlich. Jedes dieser ADO-Steuerelemente richtet eine eigene Verbindung zur Datenbank ein, was bei Datenbanken mit einer begrenzten Anzahl von verfügbaren Verbindungen zu Problemen führen kann (ganz zu schweigen vom zusätzlichen Verwaltungsaufwand in der Anwendung). Schließlich sind nicht alle datengebundenen Steuerelemente auch ADO-fähig. Da die ADO-Technologie ziemlich neu ist, gibt es momentan nur wenige Steuerelemente, die sich dafür eignen. Manche Steuerelemente erlauben zwar, daß Sie Daten abrufen und für den Benutzer anzeigen, aber der Benutzer kann die Daten nicht ändern oder bearbeiten. Andere Steuerelemente bieten nicht einmal diese Unterstützung.
Wenn Sie sich in der MFC-Klassenhierarchie umsehen, werden Sie Klassen für den
Einsatz mit ADO vermissen. Wenn Sie die Lösung mit Steuerelementen nicht einsetzen
möchten, welche Möglichkeiten gibt es sonst? Müssen Sie Klassen in eigener Regie
erstellen? Nein. Microsoft hat andere Werkzeuge bereitgestellt, um Klassen für die
Objekte in ADO zu erstellen und zu verwenden. Zu diesem Zweck gibt es eine neue
C++-Präcompilerdirektive namens #import
.
Die Präcompilerdirektive #import
ist erst mit der Version 5.0 zu Visual C++ hinzugekommen.
Mit dieser Direktive kann man eine ActiveX-DLL importieren, die mit der
Schnittstellenbeschreibung IDispatch
erstellt wurde. Diese Direktive weist den Visual
C++-Compiler an, die Objektinformationen aus der DLL herauszuziehen, wobei eine
Reihe von Header-Dateien erstellt und automatisch in Ihr Projekt aufgenommen werden.
Diese Header-Dateien weisen die Dateierweiterungen .tlh
und .tli
auf und befinden
sich im Ausgabeverzeichnis Ihres Projekts (dem Verzeichnis Debug oder Release,
demselben Verzeichnis, in dem Sie die ausführbare Anwendung finden,
nachdem Sie das Projekt kompiliert haben). Diese beiden Dateien enthalten Klassendefinitionen
für alle Objekte in der DLL, die Sie in Ihrem Code verwenden können.
Die Direktive #import
weist den Compiler auch an, die DLL als Teil des Projekts einzubinden,
wodurch die Notwendigkeit entfällt, die .lib
-Datei für die DLL in das Projekt
aufzunehmen.
Um die ADO-DLL zu importieren, schreiben Sie den folgenden Code an den Beginn der Header-Datei, in der Sie alle Datenbankobjekte definieren:
#define INITGUID
#import "C:\Programme\Gemeinsame Dateien\System\ADO\Msado15.dll"
rename_namespace("ADOCG") rename("EOF", "EndOfFile")
using namespace ADOCG;
#include "icrsint.h"
In diesen vier Zeilen definiert die erste Direktive eine Konstante, die für ADO erforderlich
ist. Die zweite Zeile importiert die ADO-DLL und erstellt die erwähnten zwei Header-Dateien.
Nach dem Namen der zu importierenden Datei enthält diese Direktive
zwei Attribute zur #import
-Direktive. Das erste, rename_namespace
, benennt den Namensbereich
um, in den die DLL importiert wurde. Daran schließt sich die Zeile nach
der #import
-Direktive an, wo der umbenannte Namensbereich als der verwendete
spezifiziert wird. Das zweite Attribut, rename
, benennt ein Element in den Header-Dateien
um, die mit der #import
-Direktive erzeugt wurden. Die Elemente in diesen Header-Dateien
sind umzubenennen, um zu verhindern, daß Konflikte mit einem anderen
Element, das an anderer Stelle benannt wurde, auftreten. Wenn Sie sich die Header-
Datei ansehen, ist das angegebene Element nicht in der Datei umbenannt, wenn aber
der Compiler die Datei liest, benennt er das Element um. Die letzte Zeile bindet die
ADO-Header-Datei mit den Definitionen verschiedener Makros ein, die Sie beim
Schreiben Ihrer ADO-Anwendungen benötigen.
Bevor man ADO-Objekte verwenden kann, muß man die COM-Umgebung für die
Anwendung initialisieren. Dazu ruft man die API-Funktion CoInitialize
auf und
übergibt NULL
als einzigen Parameter:
::CoInitialize(NULL);
Damit können Sie nun ActiveX-Objekte aufrufen. Wenn Sie diese Codezeile in Ihrer Anwendung auslassen oder erst nach der Interaktion mit den Objekten schreiben, erhalten Sie einen COM-Fehler, sobald Sie die Anwendung ausführen.
Wenn Sie alle Aktivitäten in bezug auf ADO beendet haben, müssen Sie auch die
COM-Umgebung herunterfahren. Rufen Sie dazu die Funktion CoUninitialize
wie
folgt auf:
CoUnitialize;
Diese Funktion räumt die COM-Umgebung auf und bereitet Ihre Anwendung auf das Herunterfahren vor.
Sobald die COM-Umgebung initialisiert ist, können Sie eine Verbindung zur Datenbank
herstellen. Statt eine Connection
-Objektvariable zu erzeugen, deklariert man
besser einen Connection
-Objektzeiger, _ConnectionPtr
, und verwendet ihn für alle
Zugriffe auf das Connection
-Objekt. Diesen Connection
-Objektzeiger können Sie
dann mit einer Instanz des Connection
-Objekts initialisieren. Dazu rufen Sie die Funktion
CreateInstance
auf und übergeben die UUID
des Connection
-Objekts als einzigen
Parameter:
_ConnectionPtr pConn;
pConn.CreateInstance(__uuidof(Connection));
Nachdem Sie das Objekt erzeugt haben, können Sie die Funktion Open
aufrufen, um
die Verbindung zur Datenbank einzurichten. Die Funktion Open
übernimmt vier Parameter.
Der erste gibt die Verbindungszeichenfolge an. Dieser String definiert die OLE
DB-Datenquelle für die Datenbank. Das kann ein ODBC OLE DB-Treiber sein, wobei
OLE DB über einer ODBC-Datenquelle sitzt, wie Sie sie in Ihrer Beispielanwendung
einsetzen. Wenn Sie mit SQL Server oder Oracle-Datenbanken arbeiten, kann es eine
direkte Verbindung zur OLE DB-Schnittstelle sein, die von der Datenbank selbst bereitgestellt
wird. Der zweite Parameter ist die Benutzer-ID für die Verbindung zur Datenbank.
Im dritten Parameter ist das zugehörige Kennwort anzugeben. Der vierte Parameter
bezeichnet den Cursortyp, der mit der Datenbank zum Einsatz kommt. Diese
Typen sind in der Header-Datei Msado15.tlh
definiert, die durch die #import
-Direktive
angelegt wird. Das folgende Beispiel zeigt einen typischen Einsatzfall der Funktion
Open
, um eine Verbindung zu einer ODBC-Datenquelle herzustellen, die weder Benutzer-ID
noch Kennwort benötigt:
pConn->Open(L"Provider=MSDASQL.1;Data Source=VCPP21DB", L"", L"",
adOpenUnspecified);
Nachdem Sie die Verbindung geöffnet haben, können Sie mit einem Command
-Objekt
SQL-Befehle an die Datenbank übergeben. Das ist die normale Methode, um SQL-
Befehle mit ADO auszuführen. Um ein Command
-Objekt zu erzeugen, gehen Sie genauso
vor, wie bei einem Connection
-Objekt. Sie deklarieren einen Command
-Objektzeiger,
_CommandPtr
, und erzeugen dann eine Instanz dieses Objekts mit der UUID
des Command
-Objekts:
_CommandPtr pCmd;
pCmd.CreateInstance(__uuidof(Command));
Wenn Sie Ihr Command
-Objekt erstellt und bereits eine Verbindung zur Datenbank eingerichtet
haben, setzen Sie die Eigenschaft ActiveConnection
(für die aktive Verbindung)
des Command
-Objekts auf den geöffneten Connection
-Objektzeiger:
pCmd->ActiveConnection = pConn;
Als nächstes können Sie den auszuführenden SQL-Befehl spezifizieren, indem Sie die
CommandText
-Eigenschaft des Command
-Objekts festlegen:
pCmd->CommandText = "Select * from Adressen";
Jetzt haben Sie zwei Möglichkeiten, um diesen Befehl auszuführen und die Datensätze
abzurufen. Erstens können Sie die Methode Execute
des Command
-Objekts aufrufen.
Die Methode gibt ein neues Recordset
-Objekt zurück, das Sie auf einen Recordset
-
Objektzeiger setzen:
_RecordsetPtr pRs;
pRs = pCmd->Execute();
Die andere Lösung, den Befehl auszuführen und die Datensätze abzurufen, besteht
darin, das Command
-Objekt als Quelle für die Datensätze im Recordset
festzulegen.
Dazu ist es erforderlich, das Recordset
-Objekt wie folgt zu erstellen:
_RecordsetPtr pRs;
pRs.CreateInstance(__uuidof(Recordset));
pRs->PutRefSource(pCmd);
Jetzt sind zwei NULL
-Werte vom Typ Variant
zu erzeugen, um sie als die beiden ersten
Parameter an die Methode Open
des Recordset
-Objekts zu übergeben. Der dritte Parameter
ist der zu verwendende Cursortyp gefolgt von der zu verwendenden Sperrmethode.
Schließlich enthält der fünfte Parameter an die Methode Open
des Recordset
-
Objekts ein Optionsflag, das angibt, wie die Datenbank den übergebenen Befehl auswerten
soll. Dazu schreiben Sie folgenden Code:
// NULL vom Typ Variant erzeugen
_variant_t vNull;
vNull.vt = VT_ERROR;
vNull.scode = DISP_E_PARAMNOTFOUND;
// Recordset öffnen
pRs->Open(vNull, vNull, adOpenDynamic, adLockOptimistic, adCmdUnknown);
Es gibt eine weitere Lösung, um alle obigen Aufgaben in nur ein paar Codezeilen zu
formulieren. Man läßt die Objekte Command
und Connection
einfach links liegen und
schreibt die gesamten Verbindungsinformationen in die Funktion Open
des Recordset
-
Objekts. Den SQL-Befehl kann man als ersten Parameter spezifizieren und die Verbindungsinformationen
als zweiten, statt der beiden NULL-Werte, die Sie vorher übergeben
haben. Bei dieser Methode reduziert sich der gesamte obige Code auf die folgenden
Zeilen:
_RecordsetPtr pRs;
pRs.CreateInstance(__uuidof(Recordset));
pRs->Open(_T("Provider=MSDASQL.1;Data Source=VCPP21DB"),
_T("select * from Adressen"), adOpenDynamic,
adLockOptimistic, adCmdUnknown);
Nachdem Sie eine Gruppe von Datensätzen aus der Datenbank abgerufen und in einem
Recordset
-Objekt abgelegt haben, müssen Sie durch die Datensätze navigieren.
Diese Funktionalität ist erwartungsgemäß über die Funktionen MoveFirst
(zum ersten
Datensatz), MoveLast
(zum letzten Datensatz), MovePrevious
(zum vorherigen Datensatz)
und MoveNext
(zum nächsten Datensatz) verfügbar. Die Funktionen übernehmen
keine Parameter, da sie genau die beschriebenen Aufgaben ausführen.
Neben diesen Funktionen verfügt das Recordset
-Objekt noch über die beiden Eigenschaften
BOF
und EOF
(die Sie normalerweise umbenennen sollten, um Konflikte mit
der Standarddefinition von EOF
zu vermeiden). Aus diesen Eigenschaften geht hervor,
ob der aktuelle Datensatz in der Gruppe außerhalb der Datensatzgruppe liegt.
Sobald Sie auf die Datenwerte in den einzelnen Feldern zugreifen müssen, beginnt das
Arbeiten mit ADO in Visual C++ interessant zu werden. Das Konzept von ADO zielt
auf einen einfachen Einsatz in den Skriptsprachen VBScript und JScript von Microsoft
ab. Allerdings kennen diese Sprachen nur den Datentyp Variant
, so daß alle Datenelemente,
die Sie aus den Feldern im ADO-Recordset
abrufen, Variant
-Werte
sind. Man muß sie in die Datentypen konvertieren, die man eigentlich benötigt. Dazu
gibt es zwei Wege. Beim einfacheren ruft man die Werte in einen Variant
ab und
konvertiert sie dann, wie es der folgende Code zeigt:
_variant_t vFirstName;
CString strFirstName;
vFirstName = pRs->GetCollect(_variant_t("Vorname"));
vFirstName.ChangeType(VT_BSTR);
strFirstName = vFirstName.bstrVal;
Der etwas mühsamere Weg ist tatsächlich der bessere und erleichtert auf lange Sicht
die Arbeit. Microsoft hat eine Reihe von Makros geschaffen, die die Konvertierung automatisch
vornehmen und einen Satz von Variablen der Datensätze in der Ergebnismenge
verwalten. Um damit zu arbeiten, definiert man eine neue Klasse als Schnittstelle
zum Recordset. Diese Klasse leitet man von der Klasse CADORecordBinding
ab,
die in der Header-Datei icrsint.h
definiert ist und die man unmittelbar nach der #import
-Direktive einbindet. Diese Klasse weist weder Konstruktor noch Destruktor auf,
enthält aber verschiedene Makros und eine Anzahl von Variablen. Jedes Feld in der
Datensatzmenge hat zwei Variablen, eine unsigned long
, die den Status der Variablen
verwaltet, und die Feldvariable an sich. Diese Variablen müssen reguläre C-Variablen
sein und dürfen nicht als C++-Klassen wie etwa CString
angelegt sein. Der folgende
Code zeigt ein einfaches Beispiel für diese Klassendeklaration:
class CCustomRs :
public CADORecordBinding
{
BEGIN_ADO_BINDING(CCustomRs)
ADO_FIXED_LENGTH_ENTRY(1, adInteger, m_lAddressID, lAddressIDStatus, FALSE)
ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_szFirstName, sizeof(m_szFirstName),
ÂlFirstNameStatus, TRUE)
ADO_FIXED_LENGTH_ENTRY(3, adDate, m_dtBirthdate, lBirthdateStatus, TRUE)
ADO_FIXED_LENGTH_ENTRY(4, adBoolean, m_bSendCard, lSendCardStatus, TRUE)
END_ADO_BINDING()
public:
LONG m_lAddressID;
ULONG lAddressIDStatus;
CHAR m_szFirstName[51];
ULONG lFirstNameStatus;
DATE m_dtBirthdate;
ULONG lBirthdateStatus;
VARIANT_BOOL m_bSendCard;
ULONG lSendCardStatus;
};
Den Aufbau dieser Klasse definieren Sie entsprechend der Anordnung der Datensätze, die die Datenbankabfrage zurückgibt. Dann können Sie eine Variable dieser Klasse für Ihre Anwendung wie folgt deklarieren:
CCustomRs m_rsRecSet;
Als nächstes ist ein Zeiger auf eine IADORecordBinding
-Schnittstelle zu erzeugen:
IADORecordBinding *picRs = NULL;
Es handelt sich um einen Zeiger auf eine COM-Schnittstelle, die Teil des ADO-Recordset
-Objekts ist. Wenn Sie die Gruppe der Datensätze abrufen, müssen Sie den
Zeiger auf die IADORecordBinding
-Schnittstelle abrufen und die benutzerdefinierte Recordset-Klasse
an das Recordset
-Objekt wie im folgenden Codebeispiel binden:
if (FAILED(pRs->QueryInterface(__uuidof(IADORecordBinding), (LPVOID *)&picRs)))
_com_issue_error(E_NOINTERFACE);
picRs->BindToRecordset(&m_rsRecSet);
Wenn Sie nun durch die Datensätze in der Ergebnismenge navigieren, brauchen Sie nur auf die Member-Variablen Ihrer benutzerdefinierten Datensatzklasse zuzugreifen, um den aktuellen Wert für jedes Feld abzurufen.
Den Schlüssel zur zweiten Methode des Zugriffs auf Datenwerte im Recordset bilden
die Makros, mit denen Sie die Datensatzklasse definieren. Die Gruppe der Makros beginnt
mit BEGIN_ADO_BINDING
, das den Klassennamen als einzigen Parameter übernimmt.
Das Makro richtetet die Strukturdefinition ein, die mit den nachstehend beschriebenen
restlichen Makros erzeugt wird.
Das Makro END_ADO_BINDING
schließt die Makrogruppe ab. Dieses Makro übernimmt
keine Parameter und beendet die Definition der Datensatzbindungsstruktur, die in der
Klasse erzeugt wurde. In den übrigen Makros, die zwischen diesen beiden stehen, läuft
die eigentliche Arbeit ab.
Das Makro ADO_FIXED_LENGTH_ENTRY
kommt bei Datenbankfeldern mit fester Größe
zum Einsatz. Es arbeitet mit Feldern vom Typ Datum oder Boolesch sowie mit Textfeldern,
denen eine feste Größe zugewiesen ist und es keine Möglichkeit einer Änderung
in der Datenbank gibt. Das Makro existiert in zwei Versionen, wobei man für die zweite
Version eine 2 an den Makronamen anhängt (ADO_FIXED_LENGTH_ENTRY2
).
Beide Versionen erfordern die gleichen ersten drei und den letzten Parameter. In der
ersten Version ist ein zusätzlicher Parameter erforderlich, der in der zweiten Version
optional ist. Der erste Parameter gibt die Ordinalzahl des Feldes im Recordset an. Es
handelt sich dabei um die Feldreihenfolge, wie sie die SQL-Abfrage zurückgibt, die
zum Füllen des Recordset ausgeführt wird. Der zweite Parameter bezeichnet den Datentyp
des Feldes. Die verfügbaren Dateitypen sind in der Header-Datei definiert, die
durch die #import
-Direktive erzeugt wird. Im dritten Parameter steht die Variable, in
die der Datenwert kopiert wird. Bei der ersten Version des Makros ist der vierte Parameter
die Variable für den Feldstatus (der unsigned long
, den Sie mit der Variablen
für den eigentlichen Wert definiert haben). Die letzte Variable ist ein Boolescher Wert,
der angibt, ob das Feld modifiziert werden kann.
Die Makros ADO_NUMERIC_ENTRY
verwendet man nur in Verbindung mit numerischen
Feldern. Wie die Makros ADO_FIXED_LENGTH_ENTRY
liegen sie ebenfalls in zwei Versionen
vor, die in der gleichen Weise benannt sind. Die ersten fünf und der letzte Parameter
sind in beiden Versionen gleich. Wie bei den Makros ADO_FIXED_LENGTH_ENTRY
hat die erste Version einen zusätzlichen Parameter, der in der zweiten Version nicht
verwendet wird.
Die drei ersten Parameter für die Makros ADO_NUMERIC_ENTRY
entsprechen den Parametern
für die Makros ADO_FIXED_LENGTH_ENTRY
. Gleiches gilt für den letzten Parameter
und bei der zweiten Version für den vorletzten. In den vierten und fünften Parametern
unterscheiden sich diese Makros. Der vierte Parameter legt die Genauigkeit des
Wertes in diesem Feld des Recordsets fest. Der fünfte Parameter spezifiziert die Skalierung
des Wertes. Beide Parameter sind für die Konvertierung des Wertes aus und in
den Datentyp Variant
entscheidend.
Die Makros ADO_VARIABLE_LENGTH_ENTRY
bilden den Abschluß in der Reihe der Makros.
Diese Makros verwendet man für Datenbankfelder, die sich in der Länge ändern
können. Bei einer SQL-basierten Datenbank kommen diese Makros bei allen Spalten
vom Typ varchar
(Strings variabler Länge) zum Einsatz. Das Makro existiert in drei
Versionen. Alle Versionen stimmen in den ersten vier und dem letzten Parameter
überein. In dazwischen vorkommenden Parametern unterscheiden sich die Makros.
Der erste Parameter gibt die Ordinalzahl der Spalte im Recordset an, wie er von der SQL-Abfrage zurückgegeben wird. Der zweite Parameter gibt den Datentyp an. Im dritten Parameter steht die Variable, in die der Datenwert zu übernehmen ist. Der vierte Parameter spezifiziert für alle Versionen des Makros die Größe der Variablen, in die der Wert einzutragen ist. Damit wird verhindert, daß die Daten über das Ende der Variablen, die für die Aufnahme des Wertes definiert wurde, hinaus geschrieben werden. Wie bei den vorherigen Makros gibt der letzte Parameter an, ob das Feld aktualisierbar ist.
In der ersten Version des Makros stehen zwei Parameter zwischen dem vierten und dem letzten Parameter. Die zweite Version weist an dieser Stelle nur einen Parameter auf, während in der dritten Version des Makros nur der zweite der beiden zusätzlichen Parameter vorkommt. Der erste der beiden Parameter ist die Statusvariable für das betreffende Feld. Im zweiten dieser beiden Parameter steht die Länge des Feldes in der Datenbank. Das obige Beispiel verwendet die zweite Version des Makros.
Wenn man Werte in einem Datensatz des Recordsets aktualisieren muß, hängt dessen
Behandlung von den beiden Methoden ab, nach denen man die Datenelemente aus
dem Recordset abruft. Wenn Sie jedes Feld abgerufen und in eigener Regie aus einem
Variant
konvertiert haben, müssen Sie jedes einzelne Feld, das sich geändert hat,
aktualisieren. Zu diesem Zweck verwendet man die Methode Update
des Recordset
-
Objekts. Diese Methode übernimmt zwei Variablen: das zu aktualisierende Feld und
den neuen Wert für das Feld. Der folgende Code zeigt ein Beispiel für eine derartige
Aktualisierung:
_variant_t vName, vValue;
vName.SetString("Vorname");
vValue.SetString("John");
pRs->Update(vName, vValue);
Haben Sie eine Datensatzklasse erzeugt und an den Recordset gebunden, vereinfacht
sich die Aktualisierung des Datensatzes etwas. Nachdem Sie die neuen Werte in die
Variablen der Datensatzklasse kopiert haben, können Sie die datensatzgebundene
Version der Funktion Update
aufrufen, wie es das folgende Beispiel zeigt:
picRs->Update(&m_rsRecSet);
Hier erhält das zu aktualisierende Recordset
-Objekt die Werte aus der Datensatzklasse,
die Sie an den Recordset gebunden haben.
Datensätze lassen sich in einem ADO-Recordset in ähnlicher Weise hinzufügen und löschen, wie man es von anderen Technologien des Datenbankzugriffs kennt. Allerdings gibt es kleinere Besonderheiten zu beachten, wenn man neue Datensätze hinzufügt.
Um den aktuellen Datensatz zu löschen, ruft man die Delete
-Methode des Recordset
-
Objekts auf. Diese Methode erfordert einen einzelnen Parameter, der festlegt, wie das
Löschen erfolgen soll. Höchstwahrscheinlich übergeben Sie den Wert adAffectCurrent
, so daß nur der aktuelle Datensatz im Recordset gelöscht wird, wie es der folgende
Code zeigt:
pRs->Delete(adAffectCurrent);
pRs->MovePrevious();
Analog zu anderen Technologien des Datenbankzugriffs gibt es keinen aktuellen Datensatz mehr, nachdem Sie den aktuellen Datensatz gelöscht haben. Man muß zu einem anderen Datensatz navigieren, bevor man dem Benutzer weitere Aktionen gestatten kann.
Um einen neuen Datensatz hinzuzufügen, ruft man die Methode AddNew
des Recordset
-Objekts auf. Der neu hinzugefügte Datensatz wird zum aktuellen Datensatz im Recordset.
Wenn Sie die Variablen in der von Ihnen erzeugten Datensatzklasse untersuchen,
stellen Sie fest, daß sie alle leer sind. Allerdings können Sie nicht einfach
beginnen, Datenwerte in diese Felder einzugeben. Um dem Benutzer die sofortige
Eingabe der verschiedenen Datenelemente in den neuen Datensatz zu ermöglichen,
leeren Sie die Werte in der Datensatzklasse und übergeben diese Variable als einzigen
Parameter an die Klasse AddNew
. Diese müssen Sie über den datensatzgebundenen
Schnittstellenzeiger wie im folgenden Beispiel aufrufen:
CString strBlank = " ";
COleDateTime dtBlank;
m_rsRecSet.m_lAddressID = 0;
strcpy(m_rsRecSet.m_szFirstName, (LPCTSTR)strBlank);
m_rsRecSet.m_dtBirthdate = (DATE)dtBlank;
m_rsRecSet.m_bSendCard = VARIANT_FALSE;
picRs->AddNew(&m_rsRecSet);
Auf diese Weise kann man dem Benutzer einen leeren Datensatz zur Verfügung stellen,
der zur Bearbeitung bereit ist. Nachdem der Benutzer die verschiedenen Werte in
den Datensatz eingetragen hat, kopieren Sie alle diese Werte zurück in die Datensatzvariable.
Rufen Sie dann die Methode Update
auf, um den Datensatz zu speichern.
Wenn Sie die Arbeit an einem Recordset beendet haben, schließen Sie den Recordset
durch Aufruf der Methode Close
, wie es der folgende Code zeigt:
pRs->Close();
Haben Sie die Datenbankinteraktion für die gesamte Anwendung beendet, schließen
Sie auch die Verbindung zur Datenbank. Rufen Sie dazu die Methode Close
des
Connection
-Objekts auf:
pConn->Close();
Die Beispielanwendung, die Sie heute erstellen, ist eine weitere einfache Datenbankanwendung, die grundsätzlich das gleiche wie die Anwendung der gestrigen Lektion realisiert. Allerdings rufen Sie jetzt eine Datensatzgruppe per ADO aus einer Access- Datenbank ab und stellen die Funktionalität bereit, um durch den Recordset zu navigieren. Der Benutzer kann Änderungen an den Daten im Recordset vornehmen und diese Änderungen in die Datenbank übernehmen. Außerdem lassen sich Datensätze nach Belieben neu hinzufügen und löschen. Alles das erreichen Sie mit ADO als Mittel für den Zugriff auf die Datenbank, wobei der Zugriff über den gestern konfigurierten ODBC-Treiber verläuft.
Die Beispielanwendung ist als SDI-Anwendung konzipiert. Wie bei verschiedenen anderen Anwendungen, die Sie im Verlauf dieses Buches erstellen, lassen sich alle Elemente der heutigen Anwendung ebenso auf MDI-Anwendungen oder dialogbasierte Anwendungen übertragen. Als erstes erstellen Sie für die Anwendung ein Gerüst mit dem MFC-Anwendungs-Assistenten, wobei Sie die meisten der vorgegebenen Einstellungen für eine SDI-Anwendung übernehmen.
Legen Sie zunächst mit dem Anwendungs-Assistenten ein neues Projekt an, das Sie
zum Beispiel mit DbAdo
benennen. Im ersten Dialogfeld des Assistenten wählen Sie die
Option Einzelnes Dokument (SDI). Übernehmen Sie die Standardeinstellungen im
zweiten bis fünften Schritt des Assistenten. Achten Sie darauf, daß keine Datenbankunterstützung
in die Anwendung eingebunden wird. Im letzten Schritt des Assistenten
legen Sie fest, daß die Sichtklasse (CDbAdoView
) von der Basisklasse CFormView
abzuleiten
ist.
Nachdem Sie das Anwendungsgerüst erstellt haben, entwerfen Sie das Hauptdialogfeld. Fügen Sie gemäß Abbildung 15.6 die Steuerelemente für alle Felder der Tabelle Adressen aus der gestern verwendeten Datenbank hinzu (falls Sie mit einer anderen Datenbank gearbeitet haben, nehmen Sie die Steuerelemente für die Felder der tatsächlich verwendeten Tabelle auf). Legen Sie die Eigenschaften der Steuerelemente entsprechend Tabelle 15.1 fest.
Abbildung 15.6:
Das Layout des Hauptformulars
Nachdem Sie diese Steuerelemente auf dem Formular untergebracht haben, weisen Sie den Steuerelementen mit dem Klassen-Assistenten Variablen zu, wie es aus Tabelle 15.2 hervorgeht. Die Variablen sollten mit den Datentypen der Datenbankspalten, die das jeweilige Steuerelement anzeigt, übereinstimmen.
Bevor Sie weiter an Ihrer Anwendung arbeiten, müssen Sie erst Ihre benutzerdefinierte
Datensatzklasse erstellen, die Sie an den Recordset binden. In dieser Klasse sind öffentliche
Variablen für alle Spalten in der ausgewählten Datenbanktabelle sowie Statusvariablen
für diese Spalten erforderlich. Weiterhin erstellen Sie die Gruppe von
Makros, um die Spaltenwerte zwischen dem Recordset und den Klassenvariablen auszutauschen.
Um die Klasse zu erstellen, legen Sie eine neue Klasse nach der gleichen
Methode wie in den vergangenen Tagen an. Als Typ wählen Sie Allgemeine Klasse.
Vergeben Sie einen passenden Klassennamen wie etwa CCustomRs
, und legen Sie
CADORecordBinding
als Basisklasse mit Zugriffsstatus public
fest.
In der neu erstellten Klasse löschen Sie den Konstruktor und den Destruktor sowohl aus den Header- als auch den Quellcodedateien für die neue Klasse. Bearbeiten Sie die Header-Datei für die neue Klasse, um die ADO-DLL zu importieren sowie die Makros und Variablen aufzunehmen, wie es Listing 15.1 zeigt.
Listing 15.1: Die benutzerdefinierte Datensatzklasse
1: #define INITGUID
2: #import "C:\Programme\Gemeinsame Dateien\System\ADO\msado15.dll"
Ârename_namespace("ADOCG") rename("EOF", "EndOfFile")
3: using namespace ADOCG;
4: #include "icrsint.h"
5:
6: class CCustomRs :
7: public CADORecordBinding
8: {
9: BEGIN_ADO_BINDING(CCustomRs)
10: ADO_FIXED_LENGTH_ENTRY(1, adInteger, m_lAddressID, lAddressIDStatus,
ÂFALSE)
11: ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_szFirstName,
Âsizeof(m_szFirstName), lFirstNameStatus, TRUE)
12: ADO_VARIABLE_LENGTH_ENTRY2(3, adVarChar, m_szLastName,
Âsizeof(m_szLastName), lLastNameStatus, TRUE)
13: ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_szSpouseName,
Âsizeof(m_szSpouseName), lSpouseNameStatus, TRUE)
14: ADO_VARIABLE_LENGTH_ENTRY2(5, adVarChar, m_szAddress, sizeof(m_szAddress),
ÂlAddressStatus, TRUE)
15: ADO_VARIABLE_LENGTH_ENTRY2(6, adVarChar, m_szCity, sizeof(m_szCity),
ÂlCityStatus, TRUE)
16: ADO_VARIABLE_LENGTH_ENTRY2(7, adVarChar, m_szStateOrProvince,
Âsizeof(m_szStateOrProvince), lStateOrProvinceStatus, TRUE)
17: ADO_VARIABLE_LENGTH_ENTRY2(8, adVarChar, m_szPostalCode,
Âsizeof(m_szPostalCode), lPostalCodeStatus, TRUE)
18: ADO_VARIABLE_LENGTH_ENTRY2(9, adVarChar, m_szCountry, sizeof(m_szCountry),
ÂlCountryStatus, TRUE)
19: ADO_VARIABLE_LENGTH_ENTRY2(10, adVarChar, m_szEmailAddress,
Âsizeof(m_szEmailAddress), lEmailAddressStatus, TRUE)
20: ADO_VARIABLE_LENGTH_ENTRY2(11, adVarChar, m_szHomePhone,
Âsizeof(m_szHomePhone), lHomePhoneStatus, TRUE)
21: ADO_VARIABLE_LENGTH_ENTRY2(12, adVarChar, m_szWorkPhone,
Âsizeof(m_szWorkPhone), lWorkPhoneStatus, TRUE)
22: ADO_VARIABLE_LENGTH_ENTRY2(13, adVarChar, m_szWorkExtension,
Âsizeof(m_szWorkExtension), lWorkExtensionStatus, TRUE)
23: ADO_VARIABLE_LENGTH_ENTRY2(14, adVarChar, m_szFaxNumber,
Âsizeof(m_szFaxNumber), lFaxNumberStatus, TRUE)
24: ADO_FIXED_LENGTH_ENTRY(15, adDate, m_dtBirthdate, lBirthdateStatus, TRUE)
25: ADO_FIXED_LENGTH_ENTRY(16, adBoolean, m_bSendCard, lSendCardStatus, TRUE)
26: ADO_VARIABLE_LENGTH_ENTRY2(17, adLongVarChar, m_szNotes,
Âsizeof(m_szNotes), lNotesStatus, TRUE)
27: END_ADO_BINDING()
28:
29: public:
30: LONG m_lAddressID;
31: ULONG lAddressIDStatus;
32: CHAR m_szFirstName[51];
33: ULONG lFirstNameStatus;
34: CHAR m_szLastName[51];
35: ULONG lLastNameStatus;
36: CHAR m_szSpouseName[51];
37: ULONG lSpouseNameStatus;
38: CHAR m_szAddress[256];
39: ULONG lAddressStatus;
40: CHAR m_szCity[51];
41: ULONG lCityStatus;
42: CHAR m_szStateOrProvince[21];
43: ULONG lStateOrProvinceStatus;
44: CHAR m_szPostalCode[21];
45: ULONG lPostalCodeStatus;
46: CHAR m_szCountry[51];
47: ULONG lCountryStatus;
48: CHAR m_szEmailAddress[51];
49: ULONG lEmailAddressStatus;
50: CHAR m_szHomePhone[31];
51: ULONG lHomePhoneStatus;
52: CHAR m_szWorkPhone[31];
53: ULONG lWorkPhoneStatus;
54: CHAR m_szWorkExtension[21];
55: ULONG lWorkExtensionStatus;
56: CHAR m_szFaxNumber[31];
57: ULONG lFaxNumberStatus;
58: DATE m_dtBirthdate;
59: ULONG lBirthdateStatus;
60: VARIANT_BOOL m_bSendCard;
61: ULONG lSendCardStatus;
62: CHAR m_szNotes[65536];
63: ULONG lNotesStatus;
64: };
Nachdem Sie diese Klasse erstellt haben, ist eine Variable in die Dokumentklasse aufzunehmen.
Fügen Sie eine neue Member-Variable in die Dokumentklasse ein, wobei
Sie den Variablentyp als CCustomRs
, den Namen als m_rsRecSet
und den Zugriff als
Privat festlegen. Weiterhin müssen Sie die Header-Datei der benutzerdefinierten Datensatzklasse
in die Quelldatei des Dokuments aufnehmen, wie es aus Listing 15.2
hervorgeht.
Listing 15.2: Die #include-Dateien in der Quelldatei des Dokuments
1: // DbAdoDoc.cpp : Implementierung der Klasse CDbAdoDoc
2: //
3:
4: #include "stdafx.h"
5: #include "dbado.h"
6:
7: #include "CustomRs.h"
8: #include "DbAadoDoc.h"
9: #include "DbAdoView.h"
Bevor es weitergeht, müssen Sie erst darauf achten, daß Sie der Ansicht eine Möglichkeit
bieten, einen Zeiger auf die Datensatzklasse von der Dokumentklasse zu erhalten.
Diese Funktion sollte einen Zeiger auf die Variable der Datensatzklasse zurückgeben.
Um die Funktion in Ihre Anwendung aufzunehmen, fügen Sie der Dokumentklasse
eine neue Member-Funktion hinzu, wobei Sie den Funktionstyp mit CCustomRs*
, die
Funktionsdeklaration mit GetRecSet
und den Zugriff als Public festlegen. In die Funktion
schreiben Sie den Code aus Listing 15.3.
Listing 15.3: Die Funktion GetRecSet der Klasse CDbAdoDoc
1: CCustomRs* CDbAdoDoc::GetRecSet()
2: {
3: // Zeiger auf das Datensatzobjekt zurückgeben
4: return &m_rsRecSet;
5: }
Der letzte Teil der Funktionalität, die Sie realisieren müssen, bevor es wirklich in medias
res mit der ADO-Programmierung geht, ist die Funktion, die Fehler von ADO
und der Datenbank meldet. Diese Funktion zeigt dem Benutzer eine Meldung mit dem
Fehlercode und einer Beschreibung an. Fügen Sie dazu eine neue Member-Funktion
in die Dokumentklasse ein. Legen Sie den Funktionstyp als void
, die Funktionsdeklaration
mit GenerateError(HRESULT hr, PWSTR pwszDescription)
und den Zugriff als
Public fest. Geben Sie in die Funktion den Code gemäß Listing 15.4 ein.
Listing 15.4: Die Funktion GenerateError der Klasse CDbAdoDoc
1: void CDbAdoDoc::GenerateError(HRESULT hr, PWSTR pwszDescription)
2: {
3: CString strError;
4:
5: // Fehlermeldung formatieren und anzeigen
6: strError.Format("Laufzeitfehler '%d (%x)'", hr, hr);
7: strError += "\n\n";
8: strError += pwszDescription;
9:
10: AfxMessageBox(strError);
11: }
In der Funktion OnNewDocument
in der Dokumentklasse lassen sich alle Aufgaben in
bezug auf die Verbindung zur Datenbank und das Abrufen des Recordsets realisieren.
Bevor Sie diese Funktionalität hinzufügen können, müssen Sie weitere Variablen in
die Dokumentklasse aufnehmen. Es sind ein Recordset
-Objektzeiger, ein IADORecordBinding
-Schnittstellenzeiger, verschiedene Zeichenfolgenvariablen zur Aufnahme
der Verbindungszeichenfolge für die Datenbank und des auszuführenden SQL-Befehls,
der den Recordset füllt, erforderlich. Diese Variablen fügen Sie der Dokumentklasse
gemäß Tabelle 15.3 hinzu.
In der Funktion OnNewDocument
führen Sie verschiedene Schritte aus, um die Verbindung
herzustellen und den Recordset abzurufen. Zuerst legen Sie die Strings für die
Datenbankverbindung und den auszuführenden SQL-Befehl fest. Als nächstes initialisieren
Sie die COM-Umgebung und setzen die beiden Zeiger auf den Anfangswert
NULL
. Mit der Funktion CreateInstance
erzeugen Sie das Recordset
-Objekt. Öffnen
Sie das Recordset
-Objekt, wobei Sie gleichzeitig die Verbindung zur Datenbank herstellen
und den SQL-Befehl ausführen. Binden Sie die Datensatzklasse mit Hilfe des
Schnittstellenzeigers IADORecordBinding
an den Recordset. Schließlich weisen Sie die
Ansichtsklasse an, die gebundenen Daten zu aktualisieren und dabei mit einer in Kürze
zu erstellenden Funktion den anfänglichen Datensatz für den Benutzer anzuzeigen.
Um die genannte Funktionalität zu realisieren, bearbeiten Sie die Funktion OnNewDocument
in der Dokumentklasse und nehmen den Code ab Zeile 8 von Listing 15.5 auf.
Listing 15.5: Die Funktion OnNewDocument der Klasse CDbAdoDoc
1: BOOL CDbAdoDoc::OnNewDocument()
2: {
3: if (!CDocument::OnNewDocument())
4: return FALSE;
5:
6: // ZU ERLEDIGEN: Hier Code zur Reinitialisierung einfügen
7: // (SDI-Dokumente verwenden dieses Dokument)
8: // Verbindungszeichenfolge und SQL-Befehl festlegen
9: m_strConnection = _T("Provider=MSDASQL.1;Data Source=VCPP21DB");
10: m_strCmdText = _T("select * from Adressen");
11:
12: // Zeiger auf Recordset und Bindung initialisieren
13: m_pRs = NULL;
14: m_piAdoRecordBinding = NULL;
15: // COM-Umgebung initialisieren
16: ::CoInitialize(NULL);
17: try
18: {
19: // Datensatzobjekt erzeugen
20: m_pRs.CreateInstance(__uuidof(Recordset));
21:
22: // Datensatzobjekt öffnen
23: m_pRs->Open((LPCTSTR)m_strCmdText, (LPCTSTR)m_strConnection,
24: adOpenDynamic, adLockOptimistic, adCmdUnknown);
25:
26: // Zeiger auf Bindungsschnittstelle des Datensatzes holen
27: if (FAILED(m_pRs->QueryInterface(__uuidof(IADORecordBinding),
28: (LPVOID *)&m_piAdoRecordBinding)))
29: _com_issue_error(E_NOINTERFACE);
30: // Datensatzklasse an Recordset binden
31: m_piAdoRecordBinding->BindToRecordset(&m_rsRecSet);
32:
33: // Zeiger auf Ansicht holen
34: POSITION pos = GetFirstViewPosition();
35: CDbAdoView* pView = (CDbAdoView*)GetNextView(pos);
36: if (pView)
37: // Datensatzgruppe mit Formular synchronisieren
38: pView->RefreshBoundData();
39: }
40: // Fehler vorhanden?
41: catch (_com_error &e)
42: {
43: // Fehler anzeigen
44: GenerateError(e.Error(), e.Description());
45: }
46:
47: return TRUE;
48: }
Bevor Sie weitergehen, sollten Sie zunächst mit entsprechendem Code sicherstellen,
daß beim Schließen der Anwendung alles ordnungsgemäß aufgeräumt wird. Sie müssen
den Recordset schließen und den Zeiger auf die Bindungsschnittstelle des Datensatzes
freigeben. Weiterhin ist die COM-Umgebung herunterzufahren. Zu diesem
Zweck fügen Sie eine Behandlungsfunktion für die Nachricht DeleteContents
in die
Dokumentklasse ein. Nehmen Sie in die Funktion den Code aus Listing 15.6 auf.
Listing 15.6: Die Funktion DeleteContents der Klasse CDbAdoDoc
1: void CDbAdoDoc::DeleteContents()
2: {
3: // TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen
4: // Recordset schließen
5: if (m_pRs)
6: m_pRs->Close();
7: // Ist Zeiger auf Datensatzbindung gültig?
8: if (m_piAdoRecordBinding)
9: // Freigeben
10: m_piAdoRecordBinding->Release();
11: // Recordset-Zeiger auf NULL setzen
12: m_pRs = NULL;
13:
14: // COM-Umgebung herunterfahren
15: CoUninitialize();
16:
17: CDocument::DeleteContents();
18: }
Um die Spaltenwerte des Datensatzes für den Benutzer anzuzeigen, fügen Sie eine
Funktion hinzu, die die Werte aus der Datensatzklasse in die Ansichtsvariablen kopiert.
Diese Funktion muß zunächst einen Zeiger auf die Datensatzklasse aus der Dokumentklasse
ermitteln. Als nächstes prüft die Funktion den Status der einzelnen Felder
in der Datensatzklasse, um sich zu vergewissern, daß der Kopiervorgang starten
kann. Anschließend wird der Wert kopiert. Nachdem Sie alle Werte kopiert haben, rufen
Sie die Funktion UpdateData
auf, um die Werte in den Steuerelementen auf dem
Formular anzuzeigen. Fügen Sie dazu eine neue Member-Funktion in die Ansichtsklasse
ein. Legen Sie den Funktionstyp als void
, die Funktionsdeklaration mit RefreshBoundData
und den Zugriff als Public fest. In die Funktion übernehmen Sie den Code
aus Listing 15.7.
Listing 15.7: Die Funktion RefreshBoundData der Klasse CDbAdoView
1: void CDbAdoView::RefreshBoundData()
2: {
3: CCustomRs* pRs;
4:
5: // Zeiger auf Dokumentobjekt holen
6: pRs = GetDocument()->GetRecSet();
7:
8: // Ist das Feld OK?
9: if (adFldOK == pRs->lAddressIDStatus)
10: // Wert kopieren
11: m_lAddressID = pRs->m_lAddressID;
12: else
13: // Andernfalls Wert auf 0 setzen
14: m_lAddressID = 0;
15: // Ist das Feld OK?
16: if (adFldOK == pRs->lFirstNameStatus)
17: // Wert kopieren
18: m_strFirstName = pRs->m_szFirstName;
19: else
20: // Andernfalls Wert auf 0 setzen
21: m_strFirstName = _T("");
22: if (adFldOK == pRs->lLastNameStatus)
23: m_strLastName = pRs->m_szLastName;
24: else
25: m_strLastName = _T("");
26: if (adFldOK == pRs->lSpouseNameStatus)
27: m_strSpouseName = pRs->m_szSpouseName;
28: else
29: m_strSpouseName = _T("");
30: if (adFldOK == pRs->lAddressStatus)
31: m_strAddress = pRs->m_szAddress;
32: else
33: m_strAddress = _T("");
34: if (adFldOK == pRs->lCityStatus)
35: m_strCity = pRs->m_szCity;
36: else
37: m_strCity = _T("");
38: if (adFldOK == pRs->lStateOrProvinceStatus)
39: m_strStateOrProvince = pRs->m_szStateOrProvince;
40: else
41: m_strStateOrProvince = _T("");
42: if (adFldOK == pRs->lPostalCodeStatus)
43: m_strPostalCode = pRs->m_szPostalCode;
44: else
45: m_strPostalCode = _T("");
46: if (adFldOK == pRs->lCountryStatus)
47: m_strCountry = pRs->m_szCountry;
48: else
49: m_strCountry = _T("");
50: if (adFldOK == pRs->lEmailAddressStatus)
51: m_strEmailAddress = pRs->m_szEmailAddress;
52: else
53: m_strEmailAddress = _T("");
54: if (adFldOK == pRs->lHomePhoneStatus)
55: m_strHomePhone = pRs->m_szHomePhone;
56: else
57: m_strHomePhone = _T("");
58: if (adFldOK == pRs->lWorkPhoneStatus)
59: m_strWorkPhone = pRs->m_szWorkPhone;
60: else
61: m_strWorkPhone = _T("");
62: if (adFldOK == pRs->lWorkExtensionStatus)
63: m_strWorkExtension = pRs->m_szWorkExtension;
64: else
65: m_strWorkExtension = _T("");
66: if (adFldOK == pRs->lFaxNumberStatus)
67: m_strFaxNumber = pRs->m_szFaxNumber;
68: else
69: m_strFaxNumber = _T("");
70: if (adFldOK == pRs->lBirthdateStatus)
71: m_oledtBirthdate = pRs->m_dtBirthdate;
72: else
73: m_oledtBirthdate = 0L;
74: if (adFldOK == pRs->lSendCardStatus)
75: m_bSendCard = VARIANT_FALSE == pRs->m_bSendCard ? FALSE : TRUE;
76: else
77: m_bSendCard = FALSE;
78: if (adFldOK == pRs->lNotesStatus)
79: m_strNotes = pRs->m_szNotes;
80: else
81: m_strNotes = _T("");
82:
83: // Daten mit Steuerelementen synchronisieren
84: UpdateData(FALSE);
85: }
Wenn Sie Änderungen zurück in den Recordset kopieren müssen, verläuft das Kopieren
der Daten aus den Steuerelementen auf dem Formular in die Variablen der Datensatzklasse
in umgekehrter Richtung. Dabei können Sie alle Werte kopieren, ob diese
sich geändert haben oder nicht, oder Sie vergleichen die beiden Werte auf Änderungen,
um zu entscheiden, welche zurückkopiert werden müssen. Die betreffende Funktion
rufen Sie auf, bevor der Benutzer zu anderen Datensätze im Recordset navigieren
kann, damit alle vom Benutzer vorgenommenen Änderungen in der Datenbank gespeichert
werden. Zu diesem Zweck fügen Sie eine neue Member-Funktion in die Ansichtsklasse
ein. Legen Sie den Funktionstyp als void
, die Funktionsdeklaration mit
UpdateBoundData
und den Zugriff als Privat fest. Nehmen Sie den Code aus Listing
15.8 in die Funktion auf.
Listing 15.8: Die Funktion UpdateBoundData der Klasse CDbAdoView
1: void CDbAdoView::UpdateBoundData()
2: {
3: CCustomRs* pRs;
4:
5: // Zeiger auf Dokument holen
6: pRs = GetDocument()->GetRecSet();
7:
8: // Steuerelemente mit Variablen synchronisieren
9: UpdateData(TRUE);
10:
11: // Feld geändert? Wenn ja, Wert zurück kopieren
12: if (m_lAddressID != pRs->m_lAddressID)
13: pRs->m_lAddressID = m_lAddressID;
14: if (m_strFirstName != pRs->m_szFirstName)
15: strcpy(pRs->m_szFirstName, (LPCTSTR)m_strFirstName);
16: if (m_strLastName != pRs->m_szLastName)
17: strcpy(pRs->m_szLastName, (LPCTSTR)m_strLastName);
18: if (m_strSpouseName != pRs->m_szSpouseName)
19: strcpy(pRs->m_szSpouseName, (LPCTSTR)m_strSpouseName);
20: if (m_strAddress != pRs->m_szAddress)
21: strcpy(pRs->m_szAddress, (LPCTSTR)m_strAddress);
22: if (m_strCity != pRs->m_szCity)
23: strcpy(pRs->m_szCity, (LPCTSTR)m_strCity);
24: if (m_strStateOrProvince != pRs->m_szStateOrProvince)
25: strcpy(pRs->m_szStateOrProvince, (LPCTSTR)m_strStateOrProvince);
26: if (m_strPostalCode != pRs->m_szPostalCode)
27: strcpy(pRs->m_szPostalCode, (LPCTSTR)m_strPostalCode);
28: if (m_strCountry != pRs->m_szCountry)
29: strcpy(pRs->m_szCountry, (LPCTSTR)m_strCountry);
30: if (m_strEmailAddress != pRs->m_szEmailAddress)
31: strcpy(pRs->m_szEmailAddress, (LPCTSTR)m_strEmailAddress);
32: if (m_strHomePhone != pRs->m_szHomePhone)
33: strcpy(pRs->m_szHomePhone, (LPCTSTR)m_strHomePhone);
34: if (m_strWorkPhone != pRs->m_szWorkPhone)
35: strcpy(pRs->m_szWorkPhone, (LPCTSTR)m_strWorkPhone);
36: if (m_strWorkExtension != pRs->m_szWorkExtension)
37: strcpy(pRs->m_szWorkExtension, (LPCTSTR)m_strWorkExtension);
38: if (m_strFaxNumber != pRs->m_szFaxNumber)
39: strcpy(pRs->m_szFaxNumber, (LPCTSTR)m_strFaxNumber);
40: if (((DATE)m_oledtBirthdate) != pRs->m_dtBirthdate)
41: pRs->m_dtBirthdate = (DATE)m_oledtBirthdate;
42: if (m_bSendCard == TRUE)
43: pRs->m_bSendCard = VARIANT_TRUE;
44: else
45: pRs->m_bSendCard = VARIANT_FALSE;
46: if (m_strNotes != pRs->m_szNotes)
47: strcpy(pRs->m_szNotes, (LPCTSTR)m_strNotes);
48: }
Die Navigation durch den Recordset unterstützen Sie in Ihrer Anwendung mit einer
Reihe von Menüs für die vier grundlegenden Navigationsbefehle: Erster, Vorheriger,
Nächster und Letzter. Da das Recordset
-Objekt und die Schnittstellenzeiger zur Datensatzbindung
im Dokumentobjekt angelegt sind, muß man die Nachrichten für diese
Menüs an die Dokumentklasse weiterreichen, um den aktuellen Datensatz zu aktualisieren
und dann zum ausgewählten Datensatz weiterzuschalten. Allerdings muß die
Ansichtsklasse die Nachrichten zuerst empfangen, da sie die geänderten Werte aus
den Steuerelementen auf dem Formular zurückkopieren muß, bevor der Recordset aktualisiert
wird. Ist die Navigation beendet, muß die Ansicht wiederum das Formular mit
den neuen Spaltenwerten des Datensatzes in Übereinstimmung bringen. Wenn man
die Sequenz berücksichtigt, wie die Nachrichten zu übergeben sind, ist es am sinnvollsten,
die Behandlungsroutine in der Ansichtsklasse unterzubringen und von hier aus
die Behandlungsroutine für die Dokumentklasse aufzurufen.
Um die beschriebene Funktionalität in der Anwendung zu realisieren, fügen Sie vier Menübefehle und die korrespondierenden Symbolleistenschaltflächen hinzu. Mit dem Klassen-Assistenten erstellen Sie in der Ansichtsklasse Behandlungsroutinen für die Nachrichten der vier Menübefehle. In die Behandlungsroutine für den Menübefehl Erster nehmen Sie den Code aus Listing 15.9 auf.
Listing 15.9: Die Funktion OnDataFirst der Klasse CDbAdoView
1: void CDbAdoView::OnDataFirst()
2: {
3: // TODO: Code für Befehlsbehandlungsroutine hier einfügen
4: // Aktuellen Datensatz aktualisieren
5: UpdateBoundData();
6: // Zum ersten Datensatz gehen
7: GetDocument()->MoveFirst();
8: // Formular mit Daten des neuen Datensatzes aktualisieren
9: RefreshBoundData();
10: }
Als nächstes fügen Sie die Funktion MoveFirst
(zum ersten Datensatz gehen) in die
Dokumentklasse ein und realisieren die eigentliche Funktionalität für den Recordset
für diese Funktion. Nehmen Sie dazu eine Member-Funktion in die Dokumentklasse
Ihrer Anwendung auf. Legen Sie den Funktionstyp als void
, die Deklaration mit MoveFirst
und den Zugriff als Public fest. In die Funktion schreiben Sie den Code aus Listing
15.10.
Listing 15.10: Die Funktion MoveFirst der Klasse CDbAdoDoc
1: void CDbAdoDoc::MoveFirst()
2: {
3: try
4: {
5: // Aktuellen Datensatz aktualisieren
6: m_piAdoRecordBinding->Update(&m_rsRecSet);
7: // Zum ersten Datensatz gehen
8: m_pRs->MoveFirst();
9: }
10: // Fehler?
11: catch (_com_error &e)
12: {
13: // Fehlermeldung generieren
14: GenerateError(e.Error(), e.Description());
15: }
16: }
Für die ADO-Funktionen MovePrevious
(zum vorherigen Datensatz), MoveNext
(zum
nächsten) und MoveLast
(zum letzten) fügen Sie entsprechende Funktionspaare in die
Ansichts- und Dokumentklasse hinzu. Wenn Sie diese Schritte abgeschlossen haben,
können Sie Ihre Anwendung kompilieren und ausführen. Mit der Anwendung ist es
jetzt möglich, die Datenbanktabelle Adressen zu öffnen und die einzelnen Datensätze
anzuzeigen, zu bearbeiten und zu aktualisieren (siehe Abbildung 15.7).
Abbildung 15.7:
Die laufende Anwendung
In der momentanen Entwicklungsphase der Anwendung können Sie Datensätze aus der Datenbanktabelle abrufen und in der Ergebnismenge - dem Recordset - navigieren. Es wäre wünschenswert, wenn man auch neue Datensätze in die Tabelle aufnehmen könnte. Diese Funktionalität läßt sich in genau der gleichen Weise umsetzen, wie Sie es für die Navigation vorgenommen haben. Zu diesem Zweck fügen Sie einen Menübefehl hinzu, lösen über diesen Menübefehl eine Behandlungsroutine in der Ansichtsklasse aus, schreiben die aktuellen Werte des Datensatzes zurück in den Recordset, rufen eine Funktion in der Dokumentklasse auf und bringen den aktuellen Datensatz aus dem Recordset auf den neuesten Stand. In bezug auf das Menü und die Ansichtsklasse ändert sich gegenüber den Menübefehlen und Funktionen zur Navigation lediglich die ID des Menüs und der Name der aufgerufenen Funktion, praktisch genau wie bei den verschiedenen Navigationsfunktionen. In der Funktion der Dokumentklasse liegen die eigentlichen funktionellen Unterschiede.
Damit sich ein neuer Datensatz hinzufügen läßt, stellen Sie sicher, daß nach dem Aktualisieren des aktuellen Datensatzes in der Dokumentklasse das Hinzufügen eines neuen Datensatzes möglich ist. Wenn diese Möglichkeit besteht, bauen Sie einen leeren Datensatz zusammen und fügen ihn in den Recordset ein. Nachdem Sie den leeren Datensatz aufgenommen haben, navigieren Sie zum letzten Datensatz in der Ergebnismenge, da dieser den neuen Datensatz repräsentiert. Jetzt können Sie die Funktion verlassen und der Ansichtsklasse die Aufgabe übertragen, das Formular mit den Datenwerten aus dem neuen - leeren - Datensatz zu aktualisieren.
Um diese Funktionalität in der Anwendung zu realisieren, fügen Sie einen neuen Menübefehl
für das Hinzufügen eines neuen Datensatzes ein. In die Ansichtsklasse nehmen
Sie eine Behandlungsroutine für den neuen Menübefehl auf. In diese Funktion
schreiben Sie den gleichen Code wie für die Navigationsfunktionen, rufen aber die
Funktion AddNew
in der Dokumentklasse auf. Als nächstes erstellen Sie die Funktion
AddNew
in der Dokumentklasse. Nehmen Sie dazu mit dem Klassen-Assistenten eine
neue Member-Funktion in die Dokumentklasse auf. Legen Sie den Typ als void
, die
Deklaration mit AddNew
und den Zugriff als Public fest. In die Funktion schreiben Sie
den Code aus Listing 15.11.
Listing 15.11: Die Funktion AddNew der Klasse CDbAdoDoc
1: void CDbAdoDoc::AddNew()
2: {
3: try
4: {
5: // Aktuellen Datensatz aktualisieren
6: m_piAdoRecordBinding->Update(&m_rsRecSet);
7: // Kann ein neuer Datensatz hinzugefügt werden?
8: if (m_pRs->Supports(adAddNew))
9: {
10: // Leeren Datensatz erzeugen
11: CreateBlankRecord();
12: // Leeren Datensatz hinzufügen
13: m_piAdoRecordBinding->AddNew(&m_rsRecSet);
14: // Zum letzten Datensatz gehen
15: m_pRs->MoveLast();
16: }
17: }
18: // Fehler?
19: catch (_com_error &e)
20: {
21: // Fehlermeldung generieren
22: GenerateError(e.Error(), e.Description());
23: }
24: }
Als nächstes fügen Sie die Funktion hinzu, die den leeren Datensatz erzeugt. In dieser
Funktion setzen Sie alle Feldvariablen in der Datensatzklasse auf einen fast leeren
String (der lediglich ein Leerzeichen enthält). Um diese Funktion in die Klasse aufzunehmen,
fügen Sie eine neue Member-Funktion in die Dokumentklasse hinzu. Legen
Sie den Typ als void
, die Deklaration mit CreateBlankRecord
und den Zugriff als Privat
fest. Übernehmen Sie in diese Funktion den Code aus Listing 15.12.
Listing 15.12: Die Funktion CreateBlankRecord
1: void CDbAdoDoc::CreateBlankRecord()
2: {
3: // Als leer verwendete Werte erzeugen
4: CString strBlank = " ";
5: COleDateTime dtBlank;
6:
7: // Die einzelnen Werte im Datensatzobjekt setzen
8: m_rsRecSet.m_lAddressID = 0;
9: strcpy(m_rsRecSet.m_szFirstName, (LPCTSTR)strBlank);
10: strcpy(m_rsRecSet.m_szLastName, (LPCTSTR)strBlank);
11: strcpy(m_rsRecSet.m_szSpouseName, (LPCTSTR)strBlank);
12: strcpy(m_rsRecSet.m_szAddress, (LPCTSTR)strBlank);
13: strcpy(m_rsRecSet.m_szCity, (LPCTSTR)strBlank);
14: strcpy(m_rsRecSet.m_szStateOrProvince, (LPCTSTR)strBlank);
15: strcpy(m_rsRecSet.m_szPostalCode, (LPCTSTR)strBlank);
16: strcpy(m_rsRecSet.m_szCountry, (LPCTSTR)strBlank);
17: strcpy(m_rsRecSet.m_szEmailAddress, (LPCTSTR)strBlank);
18: strcpy(m_rsRecSet.m_szHomePhone, (LPCTSTR)strBlank);
19: strcpy(m_rsRecSet.m_szWorkPhone, (LPCTSTR)strBlank);
20: strcpy(m_rsRecSet.m_szWorkExtension, (LPCTSTR)strBlank);
21: strcpy(m_rsRecSet.m_szFaxNumber, (LPCTSTR)strBlank);
22: m_rsRecSet.m_dtBirthdate = (DATE)dtBlank;
23: m_rsRecSet.m_bSendCard = VARIANT_FALSE;
24: strcpy(m_rsRecSet.m_szNotes, (LPCTSTR)strBlank);
25: }
Wenn Sie die Anwendung jetzt kompilieren und ausführen, können Sie neue Datensätze in die Datenbanktabelle einfügen und bearbeiten.
Als krönenden Abschluß der heutigen Beispielanwendung realisieren wir das Löschen des aktuellen Datensatzes aus dem Recordset. Diese Funktion entspricht weitgehend den Funktionen zur Navigation und zum Hinzufügen. Die Anwendung erhält einen neuen Menübefehl, der eine Behandlungsroutine in der Ansichtsklasse auslöst. Die Funktion in der Ansichtsklasse realisiert die gleichen Aufgaben wie die vorherigen Funktionen: sie aktualisiert den aktuellen Datensatz, ruft die korrespondierende Funktion in der Dokumentklasse auf und bringt dann den aktuellen Datensatz im Formular auf den neuesten Stand.
Die Funktion der Dokumentklasse verfolgt beim Löschen des Datensatzes den gleichen
Weg wie beim Hinzufügen. Sie aktualisiert den aktuellen Datensatz, prüft, ob der
aktuelle Datensatz zum Löschen freigegeben ist, holt vom Benutzer eine Bestätigung
ein, ob der Datensatz wirklich gelöscht werden soll, ruft dann die Funktion Delete
auf
und navigiert zu einem anderen Datensatz im Recordset.
Um diese Funktionalität in Ihrer Anwendung zu realisieren, fügen Sie einen neuen
Menübefehl für die Löschen-Funktion hinzu und verbinden ihn mit einer Behandlungsfunktion
in der Ansichtsklasse. In die Funktion schreiben Sie den gleichen Code wie
bei den Funktionen zum Navigieren und Hinzufügen, rufen aber die Funktion Delete
in der Dokumentklasse auf. In die Dokumentklasse nehmen Sie deshalb eine neue
Member-Funktion auf, für die Sie den Typ als void
, die Deklaration mit Delete
und
den Zugriff als Public festlegen. In die Funktion schreiben Sie den Code aus Listing
15.13.
Listing 15.13: Die Funktion Delete der Klasse CDbAdoDoc
1: void CDbAdoDoc::Delete()
2: {
3: try
4: {
5: // Aktuellen Datensatz aktualisieren
6: m_piAdoRecordBinding->Update(&m_rsRecSet);
7: // Kann ein Datensatz gelöscht werden?
8: if (m_pRs->Supports(adDelete))
9: {
10: // Nachfragen, ob Benutzer wirklich diesen Datensatz löschen will
11: if (AfxMessageBox("Diesen Datensatz wirklich löschen?",
12: MB_YESNO | MB_ICONQUESTION) == IDYES)
13: {
14: // Datensatz löschen
15: m_pRs->Delete(adAffectCurrent);
16: // Zum vorherigen Datensatz gehen
17: m_pRs->MovePrevious();
18: }
19: }
20: }
21: // Fehler?
22: catch (_com_error &e)
23: {
24: // Fehlermeldung generieren
25: GenerateError(e.Error(), e.Description());
26: }
27: }
Jetzt können Sie Ihre Anwendung kompilieren und ausführen. Das Löschen beliebiger Datensätze aus dem Recordset sollte nun funktionieren.
Heute haben Sie die neueste Technologie für den Datenbankzugriff von Microsoft kennengelernt: ActiveX Data Objects (ADO). Es wurde gezeigt, wie man ADO als einfaches ActiveX-Steuerelement einsetzt, um Datenbankzugriff über datenbezogene Steuerelemente ohne zusätzliche Programmierung bereitzustellen. Sie haben die DLL importiert, die eine umfangreiche Palette von Funktionen für den Datenzugriff bereitstellt, und haben erfahren, wie man diese Funktionalität in eigenen Anwendungen verwendet und steuert. Mit der heutigen Beispielanwendung ist es möglich, Datensätze abzurufen, die Datensätze im Recordset (der Ergebnismenge) zu manipulieren und die Änderungen in die Datenbank zu übernehmen. Für den Zugriff und die Aktualisierung der Datenwerte in einem Datensatz des Recordsets haben Sie zwei verschiedene Wege kennengelernt. Vor allem wurde gezeigt, wie man mit etwas mehr Aufwand zu Beginn einen beträchtlichen Teil der Arbeit bei der Anwendungsentwicklung auf lange Sicht einspart.
Frage:
Visual C++ bietet für ADO keine Unterstützung in Form von Assistenten. Warum
sollte ich dennoch auf ADO zurückgreifen?
Antwort:
Mit ADO gibt Microsoft den Trend für die Technologien des Datenbankzugriffs
vor. Momentan haben wir es noch mit frühen Entwicklungsstufen dieser
Technologie zu tun. Zukünftig wird diese Technologie jedoch in allen Programmiersprachen
und Anwendungen zu finden sein.
Frage:
Wenn ADO den Zugriff auf meine Datenbank über ODBC realisiert, warum geht
man dann nicht gleich den direkten Weg über die ODBC-Schnittstelle, um auf die
Datenbank zuzugreifen?
Antwort:
ADO kann ODBC nutzen, um auf diejenigen Datenbanken zuzugreifen, die
keine native OLE DB-Schnittstelle haben. Wenn Sie mit Datenbanken von
Microsoft SQL Server oder von Oracle arbeiten, sind OLE DB-Schnittstellen
vorhanden. In diesen Fällen greift ADO nicht über ODBC auf die Datenbank
zu und bringt für die Anwendung ein besseres Leistungsverhalten. Mit zukünftigen
Betriebssystemen von Microsoft bringt der Einsatz von ADO wahrscheinlich
Zugriffsmöglichkeiten, die weit über konventionelle Datenbanken
hinausgehen werden. ADO ist eine neue Technologie, deren Verbreitung in
den kommenden Jahren zunehmen wird. Aus diesem Grund empfiehlt es
sich, bereits jetzt mit ADO Bekanntschaft zu schließen, damit Sie zum richtigen
Zeitpunkt darauf vorbereitet sind.
1. Was bezeichnet die Abkürzung ADO?
2. Was verwendet ADO für den Datenbankzugriff?
3. Welche Objekte sind in ADO vorhanden?
4. Wie initialisiert man die COM-Umgebung?
5. Wie verbindet man ein Connection
-Objekt mit einem Command
-Objekt?
6. Wie verbindet man ein Recordset
-Objekt mit einem Command
-Objekt und füllt den
Recordset mit Daten?
Aktivieren und deaktivieren Sie die Menübefehle und Symbolleistenschaltflächen zur
Navigation, je nachdem, ob der Recordset am Beginn der Datei (BOF
) oder am Ende
der Datei (EOF
, umbenannt zu EndOfFile
) steht.