vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 3

Tag 15

Datenbanken per ADO bearbeiten

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 ...

Was ist ADO?

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).

ADO-Objekte

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:

Daneben existieren noch Sammelobjekte für die Aufnahme von Kollektionen der Objekte Error, Parameter und Field.

Das Objekt Connection

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.

Das Objekt Error

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.

Das Objekt Command

Ü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.

Das Objekt Parameter

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 Objekt Recordset

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 Objekt Field

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.

Das ActiveX-Steuerelement ADO

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.

Die ADO-DLL importieren

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.

Mit einer Datenbank verbinden

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));

Wenn Sie mit diesen Objekten und Funktionen arbeiten, ist auf die richtige Anzahl von Unterstrichen vor den verschiedenen Objekt- und Funktionsnamen zu achten. Das Objekt _ConnectionPtr hat nur einen einzigen Unterstrich, während in __uuidof zwei Unterstriche zu schreiben sind.

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);

Befehle ausführen und Daten abrufen

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);

Auch wenn es bei einer einfachen Anwendung - wie der heute zu erstellenden - ohne weiteres möglich ist, alle Befehls- und Verbindungsinformationen in der Funktion Open des Recordset-Objekts unterzubringen, empfiehlt es sich bei Anwendungen, die mehr als ein paar Datenbankabfragen ausführen, das Connection-Objekt zu verwenden. Damit kann man eine einzelne Datenbankverbindung einrichten und diese Verbindung zur gesamten Interaktion mit der Datenbank nutzen.

Durch den Recordset navigieren

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.

Auf Feldwerte zugreifen

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.

Die Makros BEGIN_ADO_BINDING und END_ADO_BINDING

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.

Die Makros ADO_FIXED_LENGTH_ENTRY

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

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

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.

Datensätze aktualisieren

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.

Hinzufügen und Löschen

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.

Die Objekte Recordset und Connection schließen

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();

Eine Datenbankanwendung mit ADO erstellen

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.

Das Anwendungsgerüst erstellen

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.

Wenn Ihre Zeit knapp bemessen ist, können Sie das Beispiel auch vereinfachen und verschiedene Steuerelemente und Datenbankfelder auslassen. Als Schlüsselfelder brauchen Sie Adressen-Nr, Vorname, Nachname, Geburtsdatum und Karte senden. Die anderen Felder können Sie ohne weiteres in der Anwendung weglassen. Diese Felder müssen Sie in die Klasse CCustomRs einbinden, die Sie weiter hinten in diesem Kapitel erstellen.

Abbildung 15.6:
Das Layout des Hauptformulars

Tabelle 15.1: Eigenschaften der Steuerelemente für das Hauptformular der Datenbankanwendung

Objekt

Eigenschaft

Einstellung

Text

ID

Titel

IDC_STATIC

Adressen-Nr:

Bearbeitungsfeld

ID

IDC_EDIT_ADDRESSID

Text

ID

Titel

IDC_STATIC

Vorname:

Bearbeitungsfeld

ID

IDC_EDIT_FIRSTNAME

Text

ID

Titel

IDC_STATIC

Nachname:

Bearbeitungsfeld

ID

IDC_EDIT_LASTNAME

Text

ID

Titel

IDC_STATIC

Name des Ehepartners:

Bearbeitungsfeld

ID

IDC_EDIT_SPOUSENAME

Text

ID

Titel

IDC_STATIC

Adresse:

Bearbeitungsfeld

ID

IDC_EDIT_ADDRESS

Text

ID

Titel

IDC_STATIC

Ort:

Bearbeitungsfeld

ID

IDC_EDIT_CITY

Text

ID

Titel

IDC_STATIC

Bundesland:

Bearbeitungsfeld

ID

IDC_EDIT_STATEORPROVINCE

Text

ID

Titel

IDC_STATIC

Postleitzahl:

Bearbeitungsfeld

ID

IDC_EDIT_POSTALCODE

Text

ID

Titel

IDC_STATIC

Land:

Bearbeitungsfeld

ID

IDC_EDIT_COUNTRY

Text

ID

Titel

IDC_STATIC

Email-Adresse:

Bearbeitungsfeld

ID

IDC_EDIT_EMAILADDRESS

Text

ID

Titel

IDC_STATIC

Telefon/privat:

Bearbeitungsfeld

ID

IDC_EDIT_HOMEPHONE

Text

ID

Titel

IDC_STATIC

Telefon/beruflich:

Bearbeitungsfeld

ID

IDC_EDIT_WORKPHONE

Text

ID

Titel

IDC_STATIC

Durchwahl Büro:

Bearbeitungsfeld

ID

IDC_EDIT_WORKEXTENSION

Text

ID

Titel

IDC_STATIC

Faxnummer:

Bearbeitungsfeld

ID

IDC_EDIT_FAXNUMBER

Text

ID

Titel

IDC_STATIC

Geburtsdatum:

Bearbeitungsfeld

ID

IDC_EDIT_BIRTHDATE

Text

ID

Titel

IDC_STATIC

Karte senden

Kontrollkästchen

ID

IDC_CHECK_SENDCARD

Text

ID

Titel

IDC_STATIC

Anmerkungen:

Bearbeitungsfeld

ID

IDC_EDIT_NOTES

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.

Tabelle 15.2: Variablen der Steuerelemente

Objekt

Name

Kategorie

Typ

IDC_CHECK_SENDCARD

m_bSendCard

Wert

BOOL

IDC_EDIT_ADDRESS

m_strAddress

Wert

CString

IDC_EDIT_ADRESSID

n_lAddressID

Wert

long

IDC_EDIT_BIRTHDATE

m_oledtBirthdate

Wert

COleDateTime

IDC_EDIT_CITY

m_strCity

Wert

CString

IDC_EDIT_COUNTRY

m_strCountry

Wert

CString

IDC_EDIT_EMAILADDRESS

m_strEmailAddress

Wert

CString

IDC_EDIT_FAXNUMBER

m_strFaxNumber

Wert

CString

IDC_EDIT_FIRSTNAME

m_strFirstName

Wert

CString

IDC_EDIT_HOMEPHONE

m_strHomePhone

Wert

CString

IDC_EDIT_LASTNAME

m_strLastName

Wert

CString

IDC_EDIT_NOTES

m_strNotes

Wert

CString

IDC_EDIT_POSTALCODE

m_strPostalCode

Wert

CString

IDC_EDIT_SPOUSENAME

m_strSpouseName

Wert

CString

IDC_EDIT_STATEORPROVINCE

m_strStateOrProvince

Wert

CString

IDC_EDIT_WORKEXTENSION

m_strWorkExtension

Wert

CString

IDC_EDIT_WORKPHONE

m_strWorkPhone

Wert

CString

Eine benutzerdefinierte Datensatzklasse erstellen

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: }

Verbinden und Daten abrufen

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.

Tabelle 15.3: Member-Variablen der Dokumentklasse

Name

Typ

Zugriff

m_pRs

_RecordsetPtr

Privat

m_piAdoRecordBinding

IADORecordBinding*

Privat

m_strConnection

CString

Privat

m_strCmdText

CString

Privat

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: }

Das Formular füllen

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: }

Da Sie direkt mit der benutzerdefinierten Datensatzklasse, die Sie in dieser Funktion erzeugt haben, arbeiten, müssen Sie die Header-Datei für Ihre benutzerdefinierte Datensatzklasse in die Quelldatei der Ansichtsklasse einbinden, genau wie Sie es bei der Quelldatei der Dokumentklasse getan haben.

Aktualisierungen speichern

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: }

Durch den Recordset navigieren

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

Neue Datensätze hinzufügen

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.

Datensätze löschen

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.

Zusammenfassung

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.

Fragen und Antworten

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.

Workshop

Kontrollfragen

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?

Übung

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.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Visual C++ 6 in 21 Tagen, ISBN: 3-8272-2035-1