Eine große Zahl von Anwendungen greift auf Datenbanken zu. Angefangen beim persönlichen Organizer bis hin zu großen Personalsystemen in Unternehmen speichern und verwalten Datenbanken alle Datensätze, die eine Anwendung verwendet und manipuliert. Visual C++ bietet vier verschiedene Technologien für die Verwendung und den Zugriff auf Datenbanken in Ihren Anwendungen: Data Access Objects (DAO), ODBC, OLE DB und ActiveX Data Objects (ADO). In der heutigen und morgigen Lektion gehen wir auf zwei dieser Technologien näher ein und befassen uns mit den Unterschieden sowie dem Einsatz in eigenen Anwendungen.
CRecordset
den Zugriff auf eine ODBC-Datenquelle
bereitstellt,
Die meisten Anwendungen im geschäftlichen Bereich arbeiten mit Daten. Diese Anwendungen verwalten und manipulieren Datensätze, die in Datenbanken gespeichert sind. Wenn Sie eine geschäftliche Anwendung erstellen, kommen Sie um den Zugriff auf eine Datenbank nicht herum. Die Frage ist, welche Datenbank?
Auf dem Markt ist eine ganze Anzahl von Datenbanken verfügbar. Wenn Sie eine Einzelbenutzeranwendung erstellen müssen, die eigenständig auf einem einzelnen Computer läuft, können Sie die verschiedenen PC-basierten Datenbanken wie Access oder FoxPro von Microsoft oder Paradox von Borland ins Auge fassen. Bei Anwendungen, die auf große, gemeinsam genutzte Datenbanken zugreifen, kommt höchstwahrscheinlich nur eine SQL-basierte Datenbank wie SQL Server oder Oracle in Frage. Alle Datenbanken bieten grundsätzlich die gleiche Funktionalität, um Datensätze zu verwalten. Bei jeder Datenbank lassen sich je nach Bedarf einzelne oder mehrere Datensätze abrufen. Man kann auch beliebige Datensätze hinzufügen, aktualisieren oder löschen. Alle Datenbanken können die Anforderungen Ihrer Anwendungen erfüllen, so daß Sie in der Lage sein sollten, eine bestimmte Datenbank für die eine Anwendung einzusetzen und für die nächste Anwendung zu einer anderen Datenbank zu wechseln, je nachdem, welche konkreten Anforderungen in Ihrer Anwendung zu realisieren sind, welche Datenbank sich am besten für die Anwendung eignet, oder welche Datenbank Ihr Auftraggeber bevorzugt.
Wenn man von einer Datenbank zu einer anderen wechselt, begegnet man dem Problem, daß jede Datenbank mit einer anderen Schnittstelle für den Zugriff arbeitet. Das bedeutet, daß man sich mit komplett neuen Programmierverfahren und Funktionen für jede einzusetzende Datenbank beschäftigen müßte. Diese Probleme lassen sich mit der eigens für diesen Zweck entwickelten ODBC-Schnittstelle in den Griff bekommen.
Microsoft hat die Inkompatibilität der Datenbankenschnittstellen als Problem erkannt. Jede Datenbank hatte ihre eigene Anwendungsentwicklungssprache, die zwar gut in die Datenbank integriert war, aber nicht mit irgendeiner anderen Datenbank zusammenarbeitete. Daraus ergaben sich für jeden Entwickler Probleme, der eine Datenbank für die eine Anwendung und dann eine andere Datenbank für die nächste Anwendung benötigte. Der Entwickler mußte sich mit der speziellen Datenbanksprache der jeweiligen Datenbank beschäftigen und konnte nicht auf eine bekannte Sprache zurückgreifen. Damit der Programmierer mit einer beliebigen Datenbank in der bevorzugten Programmiersprache arbeiten konnte, war eine standardisierte Schnittstelle erforderlich, die mit jeder Datenbank funktioniert.
Die Schnittstelle Open Database Connectivity (ODBC) ist als standardisierte, SQL-basierte Schnittstelle integriert und in das Betriebssystem Windows integriert. Hinter dieser Schnittstelle verbergen sich für die jeweilige Datenbank Plugins, die die ODBC- Funktionsaufrufe entgegennehmen und in Aufrufe für die spezielle Schnittstelle der betreffenden Datenbank umwandeln. Die ODBC-Schnittstelle verwendet auch einen zentralen Satz von Konfigurationen zur Datenbankverbindung, wobei auch die Art und Weise standardisiert ist, wie man diese Konfigurationen festlegt und verwaltet. Damit braucht der Programmierer nur noch eine einzige Datenbankschnittstelle für alle Datenbanken zu erlernen und einzusetzen. Darüber hinaus ist es den Anbietern von Programmiersprachen möglich, ODBC-Unterstützung in ihre Sprachen und Entwicklungswerkzeuge zu integrieren, um den Datenbankzugriff nahezu transparent zu gestalten.
In der Visual C++-Entwicklungsumgebung ist der größte Teil der ODBC-Funktionalität
in zwei Klassen verkapselt: CRecordset
und CDatabase
. Die Klasse CDatabase
enthält
die Verbindungsinformationen zur Datenbank und kann in der gesamten Anwendung
gemeinsam genutzt werden. Die Klasse CRecordset
verkapselt einen Satz von Datensätze
n aus der Datenbank. Über diese Klasse kann man eine auszuführende SQL-Abfrage
spezifizieren. Die Klasse führt die Abfrage aus und verwaltet die Ergebnismenge,
die die Datenbank zurückgibt. Die Datensätze im Recordset kann man modifizieren
und aktualisieren, die Änderungen werden zurück an die Datenbank übergeben. Im
Recordset lassen sich Datensätze hinzufügen und löschen. Auch diese Änderungen
kann man zurück in die Datenbank schreiben.
Bevor die Klasse CRecordset
irgendwelche Funktionen ausführen kann, muß sie mit
einer Datenbank verbunden werden. Das realisiert man mit Hilfe der Klasse CDatabase
. Es ist keine Instanz der Klasse CDatabase
explizit zu erzeugen oder einzurichten -
die erste Instanz der Klasse CRecordset
übernimmt das automatisch. Wenn man eine
Anwendung mit dem Anwendungs-Assistenten erstellt und die Einbindung der ODBC-
Datenbankunterstützung wählt, bindet der Anwendungs-Assistent die Verbindungsinformationen
zur Datenbank in die erste von ihm erzeugte und von CRecordset
abgeleitete
Klasse ein. Wenn man diese CRecordset
-Klasse erzeugt, ohne ein CDatabase
-
Objekt zu übergeben, verwendet sie die vorgegebenen Verbindungsinformationen, die
durch den Anwendungs-Assistenten hinzugefügt wurden, um ihre eigene Datenbankverbindung
herzustellen.
Nachdem das CRecordset
-Objekt erstellt und mit der Datenbank verbunden ist, müssen
Sie den Recordset öffnen, um die Gruppe der Datensätze aus der Datenbank abzurufen.
Dazu verwenden Sie die Member-Funktion Open
des CRecordset
-Objekts.
Diese Funktion kann man ohne irgendwelche Argumente aufrufen, wenn man für alles
die Standardwerte übernehmen möchte, einschließlich der auszuführenden SQL-
Anweisung.
Das erste Argument an die Funktion Open
ist der Typ des Recordsets. Der Standardwert
dafür lautet AFX_DB_USE_DEFAULT_TYPE
. Er öffnet den Recordset als Snapshot
von Datensätzen. Tabelle 14.1 listet die vier Typen von Recordsets auf. Nur zwei dieser
Recordset-Typen sind im Anwendungs-Assistenten verfügbar, wenn Sie die Datenquelle
spezifizieren.
Das zweite Argument an die Funktion Open
ist die SQL-Anweisung, die auszuführen
ist, um den Recordset zu füllen. Übergibt man für dieses Argument eine NULL
, wird die
vom Anwendungs-Assistenten erzeugte und vorgegebene SQL-Anweisung ausgeführt.
Das dritte Argument ist eine Gruppe von Flags, mit denen Sie angeben können, wie die Gruppe der Datensätze in den Recordset abzurufen ist. Die meisten dieser Flags erfordern ein tiefergehendes Verständnis der ODBC-Schnittstelle, damit Sie Sinn und Zweck dieser Flags in Ihrer Anwendung einschätzen können. Aus diesem Grund zeigt Tabelle 14.2 lediglich ein paar dieser Flags.
Nachdem der Benutzer die Arbeit mit dem Recordset abgeschlossen hat, können Sie
die Funktion Close
aufrufen, um den Recordset zu schließen und alle vom Recordset
belegten Ressourcen freizugeben. Die Funktion Close
übernimmt keine Argumente.
Wenn Sie eine Gruppe von Datensätzen aus der Datenbank abgerufen haben, müssen
Sie in der Lage sein, durch diese Menge von Datensätzen zu navigieren (es sei denn,
Sie haben es nur mit einem einzigen Datensatz zu tun). Die Klasse CRecordset
stellt
mehrere Funktionen bereit, mit denen man im Recordset navigieren und den Benutzer
zu einem beliebigen Datensatz führen kann. Tabelle 14.3 listet die verfügbaren
Funktionen zur Navigation im Recordset auf.
Nur zwei dieser Navigations- und Informationsfunktionen - Move
und SetAbsolutePosition
- weisen Argumente auf. Die Funktion SetAbsolutePosition
übernimmt ein
einzelnes numerisches Argument, das die Zeile des Zieldatensatzes spezifiziert. Bei
Übergabe von 0
gelangen Sie zur Position BOF
(Begin Of File - Dateianfang), bei 1
zum ersten Datensatz im Recordset. Negative Werte bewirken, daß vom letzten Datensatz
im Recordset rückwärts gezählt wird. (Zum Beispiel kommen Sie mit -1
zum
letzten, mit -2
zum vorletzten Datensatz.)
Die Funktion Move
übernimmt zwei Argumente. Das erste gibt die Anzahl der zu überspringenden
Zeilen an. Es lassen sich positive und negative Werte angeben. Mit negativen
Werten navigieren Sie rückwärts durch den Recordset. Das zweite Argument
gibt die Art und Weise der Navigation durch die Menge der Zeilen an. Die möglichen
Werte für das zweite Argument sind in Tabelle 14.4 aufgeführt.
In der Regel genügt es nicht, wenn man lediglich durch die Menge der Datensätze navigieren
kann. Man muß auch in der Lage sein, neue Datensätze in den Recordset aufzunehmen,
vorhandene Datensätze zu bearbeiten und zu aktualisieren sowie Datensätze
zu löschen. Diese Aktionen sind alle möglich über die Funktionen, die die Klasse
CRecordset
bereithält. Die Funktionen, mit denen Sie diese Funktionalität realisieren
können, sind in Tabelle 14.5 aufgeführt.
Diese Funktionen weisen keine Argumente auf. Allerdings erfordern einige, daß man bestimmte Schritte einhält, damit die Funktionen ordnungsgemäß arbeiten können.
Um einen neuen Datensatz in die Datenbank aufzunehmen, rufen Sie die Funktion
AddNew
auf. Als nächstes müssen Sie Vorgabewerte in allen Feldern festlegen, die
Werte erfordern, beispielsweise Schlüsselfelder. Dann ist die Funktion Update
aufzurufen,
um den neuen Datensatz in die Datenbank aufzunehmen. Wenn Sie versuchen,
zu einem anderen Datensatz zu navigieren, bevor Sie die Funktion Update
aufgerufen
haben, geht der neue Datensatz verloren. Nachdem Sie den neuen Datensatz gespeichert
haben, müssen Sie die Funktion Requery
aufrufen, um den Recordset zu aktualisieren,
damit Sie zum neuen Datensatz navigieren und dem Benutzer die Bearbeitung
des neuen Datensatzes ermöglichen können. Diese Sequenz der Funktionsaufrufe
sieht in der Regel folgendermaßen aus:
// Einen neuen Datensatz in den Recordset einfügen
m_pSet.AddNew();
// Das Schlüsselfeld auf den neuen Datensatz setzen
m_pSet.m_AddressID = m_lNewID;
// Den neuen Datensatz in der Datenbank speichern
m_pSet.Update();
// Den Recordset aktualisieren
m_pSet.Requery();
// Zum neuen Datensatz gehen
m_pSet.MoveLast();
Den aktuellen Datensatz können Sie einfach durch Aufruf der Funktion Delete
löschen.
Ist der aktuelle Datensatz gelöscht, müssen Sie zu einem anderen Datensatz
navigieren, damit der Benutzer nicht mehr den gerade gelöschten Datensatz sieht. Sobald
der aktuelle Datensatz gelöscht wurde, gibt es keinen aktuellen Datensatz mehr,
bis Sie zu einem anderen Datensatz navigieren. Die Funktion Update
müssen Sie nicht
explizit aufrufen, weil das die Navigationsfunktionen automatisch erledigen. Damit läßt
sich der folgende Code schreiben, um den aktuellen Datensatz zu löschen:
// Den aktuellen Datensatz löschen
m_pSet.Delete();
// Zum vorherigen Datensatz gehen
m_pSet.MovePrev();
Um schließlich dem Benutzer zu ermöglichen, den aktuellen Datensatz zu bearbeiten,
rufen Sie die Funktion Edit
auf. Damit können Sie die Felder im Datensatz mit den
neuen, vom Benutzer eingegebenen oder durch Ihre Anwendung berechneten Werten
aktualisieren. Nachdem alle Änderungen am aktuellen Datensatz ausgeführt sind,
müssen Sie die Funktion Update
aufrufen, um die Änderungen zu speichern:
// Dem Benutzer die Bearbeitung des aktuellen Datensatzes ermöglichen
m_pSet.Edit();
// Datenaustausch durchführen, dabei die Felder im Recordset aktualisieren
.
.
// Änderungen des Benutzers im aktuellen Datensatz speichern
m_pSet.Update();
Vielleicht fragen Sie sich, wie man zu den Feldern in den Datensätzen gelangt, um sie
zu aktualisieren. Wenn der Anwendungs-Assistent die von CRecordset
abgeleitete
Klasse für Ihre Anwendung erstellt, fügt er alle Felder in den Datensätzen, die im Recordset
erscheinen werden, als Member-Variablen der Recordset-Klasse hinzu. Im Ergebnis
können Sie auf die Member-Variablen zugreifen, um die Datenelemente in den
Datenbankdatensätzen, die Elemente des Recordsets sind, anzusprechen und zu manipulieren.
Die heute zu erstellende Beispielanwendung konzipieren Sie als SDI-Anwendung mit ODBC-Datenbankunterstützung. Die Anwendung ruft Datensätze aus einer ODBC- Datenbank ab und erlaubt dem Benutzer, beliebige Datensätze zu bearbeiten und zu aktualisieren. Außerdem realisieren Sie die Funktionalität, um dem Benutzer zu ermöglichen, neue Datensätze in die Datenbank aufzunehmen und Datensätze aus der Datenbank zu löschen.
Bevor Sie eine Anwendung erstellen können, die eine Datenbank verwendet, brauchen Sie eine entsprechende Datenbank für Ihre Anwendung. Fast zu jeder Datenbank, die Sie für Ihre Anwendungen kaufen können, gehören Werkzeuge, mit denen sich eine neue Datenbank anlegen läßt. Zuerst müssen Sie Ihre Datenbank mit diesen Werkzeugen erstellen und dann mit dem ODBC-Administrator eine ODBC-Datenquelle für Ihre neue Datenbank konfigurieren.
Die neue Datenbank wurde für die Beispielanwendung in diesem Kapitel mit dem Datenbank-Assistent en von Access 97 erstellt. Als Vorlage für die Datenbank diente das Adreßbuch. Beim Start des Datenbank-Assistenten wählen Sie die vorgegebenen Felder, die in die Datenbank aufzunehmen sind, und die Option Beispieldaten einfügen, wie es Abbildung 14.1 zeigt. Übernehmen Sie die übrigen Einstellungen, die der Datenbank-Assistent vorgibt.
Abbildung 14.1:
Beispieldaten in die Datenbank aufnehmen
Nachdem Sie die Datenbank erstellt haben, müssen Sie eine ODBC-Datenquelle konfigurieren, die auf die gerade erstellte Datenbank verweist. Rufen Sie dazu den ODBC- Administrator auf, der sich in der Systemsteuerung von Windows 95/98 befindet.
Im Dialogfeld ODBC-Datenquellen-Administrator fügen Sie eine neue Datenquelle hinzu. Klicken Sie dazu auf die Schaltfläche Hinzufügen (siehe Abbildung 14.2). Es öffnet sich ein weiteres Dialogfeld, in dem Sie den Datenbanktreiber für die neue Datenquelle auswählen (siehe Abbildung 14.3).
Da Sie die heutige Beispielanwendung mit einer Access-Datenbank erstellen, markieren Sie den Microsoft Access-Treiber und klicken auf die Schaltfläche Fertigstellen.
Abbildung 14.2:
Der ODBC-Datenquellen-Administrator
Abbildung 14.3:
Das Dialogfeld Neue Datenquelle erstellen
Im Dialogfeld ODBC Microsoft Access 97-Setup (siehe Abbildung 14.4) geben Sie einen
kurzen, einfachen Namen für die Datenquelle an. Ihre Anwendung verwendet diesen
Namen, um die für die Datenbankverbindung zu verwendende Konfiguration der
ODBC-Datenquelle zu spezifizieren. Daher sollte der Name den Zweck der Datenbank
umreißen oder auf die Anwendung hinweisen, die mit der Datenbank arbeitet. Für die
heutige Beispielanwendung wählen Sie etwa den Namen VCPP21DB
(für Visual C++ in
21 Tagen, Datenbank) und geben dann eine Beschreibung für die Datenbank in das
nächste Feld ein.
Abbildung 14.4:
Das Dialogfeld ODBC Microsoft Access 97-Setup
Nachdem Sie einen Namen und eine Beschreibung für die Datenquelle eingegeben haben, müssen Sie festlegen, wo sich die Datenbank befindet. Klicken Sie auf die Schaltfläche Auswählen, und spezifizieren Sie dann die Access-Datenbank, die Sie bereits erstellt haben. Nachdem Sie die ODBC-Datenquelle für Ihre Datenbank konfiguriert haben, klicken Sie auf die Schaltfläche OK, um die neue Datenquelle in den ODBC-Datenquellen-Administrator aufzunehmen. Klicken Sie erneut auf OK, um die Konfiguration zu beenden und den ODBC-Datenquellen-Administrator zu schließen, da Sie nun Ihre Aufmerksamkeit auf das Erstellen der Anwendung richten können.
Die heutige Beispielanwendung legen Sie als SDI-Anwendung mit Datenbankunterstützung
an. Beginnen Sie ein neues Projekt, wählen Sie den Anwendungs-Assistenten,
und geben Sie einen passenden Namen wie etwa DbOdbc
ein.
Im ersten Dialogfeld des Anwendungs-Assistenten wählen Sie die Option Einzelnes Dokument (SDI). Im zweiten Dialogfeld wählen Sie die Option Datenbankansicht mit Dateiunterstützung und klicken auf die Schaltfläche Datenquelle, um die Datenquelle für Ihre Anwendung festzulegen. Im Dialogfeld Datenbankoptionen klicken Sie auf die Option ODBC und wählen die für Ihre Access-Datenbank erstellte ODBC- Konfiguration aus dem Listenfeld aus, wie es Abbildung 14.5 zeigt. Als Recordset-Typ können Sie entweder Snapshot oder Dynaset wählen.
Abbildung 14.5:
Das Dialogfeld Datenbankoptionen
Wenn Sie auf OK klicken, erscheint ein weiteres Dialogfeld, in dem die verfügbaren Tabellen der ausgewählten Datenbank aufgelistet sind. Markieren Sie die Tabelle Adressen, wie es Abbildung 14.6 zeigt, und klicken Sie auf OK, um das Dialogfeld zu schließen und zum Anwendungs-Assistenten zurückzukehren.
Abbildung 14.6:
Das Dialogfeld Datenbanktabellen wählen
In den übrigen Dialogfeldern des Anwendungs-Assistenten übernehmen Sie die Standardeinstellungen.
Das letzte Dialogfeld des Assistenten zeigt, daß der Assistent eine
zusätzliche Klasse angelegt hat. Wenn Sie diese Klasse markieren, sehen Sie, daß sie
von der Klasse CRecordset
abgeleitet ist. Außerdem ist festzustellen, daß die Ansichtsklasse
von der Klasse CRecordView
abgeleitet ist, die ein Nachfolger der Klasse CFormView
ist und zusätzlich die Datenbankunterstützung enthält.
Nachdem Sie das Anwendungsgerüst erstellt haben, entwerfen Sie das Layout des Hauptformulars, in dem Sie die Datensätze aus der Datenbank anzeigen und bearbeiten können. Dieses Formular läßt sich mit den Standardsteuerelementen von Visual C++ erstellen, ohne spezielle ActiveX-Steuerelemente zu benötigen. Für das Layout des Hauptformulars Ihrer Beispielanwendung können Sie sich an Abbildung 14.7 orientieren. Die Steuerelemente konfigurieren Sie mit den in Tabelle 14.6 angegebenen Eigenschaften.
Abbildung 14.7:
Der Entwurf des Hauptformulars
Nachdem Sie alle Steuerelemente auf dem Hauptformular Ihrer Anwendung hinzugefügt und konfiguriert haben, können Sie die Steuerelemente mit den Feldern der Datenbank verbinden. Wenn Sie im Klassen-Assistenten auf die Registerkarte Member- Variablen gehen, ein Steuerelement markieren und auf die Schaltfläche Variable hinzufügen klicken, erscheint das Dialogfeld Member-Variable hinzufügen. Dieses Dialogfeld enthält ein Dropdown-Kombinationsfeld, in das Sie den Variablennamen eingeben können. Wenn Sie auf den Pfeil der Dropdown-Liste klicken, öffnet sich die Liste, die bereits mit den Feldern des Recordsets gefüllt ist, wie es Abbildung 14.8 zeigt. Damit können Sie die Datenbankfelder direkt mit den Steuerelementen auf dem Formular verbinden. Fügen Sie also die in Tabelle 14.7 angegebenen Variablen hinzu.
Abbildung 14.8:
Das Dialogfeld Member-Variable hinzufügen mit Datensatzfeldern
Sicherlich haben Sie bemerkt, daß es in der Liste der Datenbankfelder kein Feld für
das Geburtsdatum gibt, das Sie mit dem Steuerelement Geburtsdatum verbinden
könnten. Wenn Sie sich die Recordset-Klasse in der Klassenansicht ansehen und den
Baum erweitern, stellen Sie fest, daß das Geburtsdatum zwar als Datenbankfeld aufgeführt,
aber nicht in der Liste der vorhandenen Spalten für die Steuerelemente verfügbar
ist. Doppelklicken Sie in der Recordset-Klasse auf das Feld für das Geburtsdatum,
um dessen Definition anzuzeigen. Hier ist zu sehen, daß die Variable m_Geburtsdatum
mit dem Typ CTime
deklariert ist. Aus diesem Grund ist die Variable nicht in der Liste
der Datenbankfelder aufgeführt, die sich mit den Steuerelementen verbinden lassen.
Es gibt weder ein Makro noch eine Funktion, mit denen man Daten zwischen einem
Steuerelement und einer Variablen vom Typ CTime
austauschen könnte. Das ist auch
deshalb ein Problem, weil der Variablentyp CTime
keine Datumswerte vor dem 31.
Dezember 1969 behandeln kann. Um dieses Datenbankfeld nutzen zu können, muß
man seine Definition vom Typ CTime
in eine Variable vom Typ COleDateTime
ändern,
wie es in Zeile 17 von Listing 14.1 geschieht. Nachdem Sie den Variablentyp dieses
Datenbankfelds geändert haben, können Sie auch die Variable mit dem Steuerelement
IDC_EDOB
verbinden.
Listing 14.1: Die Variablendeklarationen für die Datenbankfelder
1: // Feld-/Parameterdaten
2: //{{AFX_FIELD(CDbOdbcSet, CRecordset)
3: long m_Adressen_Nr;
4: CString m_Vorname;
5: CString m_Nachname;
6: CString m_EhepartnerName;
7: CString m_Adresse;
8: CString m_Ort;
9: CString m_Bundesland;
10: CString m_Postleitzahl;
11: CString m_Land;
12: CString m_EmailAdresse;
13: CString m_TelefonPrivat;
14: CString m_TelefonBeruflich;
15: CString m_Durchwahl_B_ro;
16: CString m_Faxnummer;
17: COleDateTime m_Geburtsdatum;
18: BOOL m_SendenKarte;
19: CString m_Anmerkungen;
20: //}}AFX_FIELD
Nachdem Sie den Variablentyp für die Variable m_Geburtsdatum
in der Recordset-
Klasse (CDbOdbcSet
) geändert und dieses Datenbankfeld dem Steuerelement Geburtstag
auf dem Formular zugeordnet haben, nehmen Sie vielleicht an, daß Sie die Anwendung
nun kompilieren und ausführen können. Leider erhalten Sie eine Fehlermeldung
des Compilers, daß DDX_FieldText
den Variablentyp COleDateTime
nicht
konvertieren kann. Sie müssen also den Code zur Umwandlung in eigener Regie hinzufügen.
Kehren Sie zum Klassen-Assistenten zurück, und löschen Sie die Variable,
die Sie für das Steuerelement IDC_EDOB
hinzugefügt haben. Nehmen Sie eine neue
Variable für dieses Steuerelement auf. Legen Sie für die Variable den Typ COleDateTime
fest, und geben Sie der Variablen einen Namen wie m_oledtDOB
. Laden Sie die
Funktion DoDataExchange
der Sichtklasse (CDbOdbcView
) in den Editor, und fügen Sie
die Zeilen 4 bis 6 sowie die Zeilen 25 bis 28 gemäß Listing 14.2 in die Funktion ein.
Listing 14.2: Die Funktion DoDataExchange der Klasse CDbOdbcView
1: void CDbOdbcView::DoDataExchange(CDataExchange* pDX)
2: {
3: CRecordView::DoDataExchange(pDX);
4: // Geburtsdatum aus Recordset in Ansichtsvariable kopieren
5: if (pDX->m_bSaveAndValidate == FALSE)
6: m_oledtDOB = m_pSet->m_Geburtsdatum;
7: //{{AFX_DATA_MAP(CDbOdbcView)
8: DDX_FieldText(pDX, IDC_EADDR, m_pSet->m_Adresse, m_pSet);
9: DDX_FieldCheck(pDX, IDC_CBCARD, m_pSet->m_SendenKarte, m_pSet);
10: DDX_FieldText(pDX, IDC_ECITY, m_pSet->m_Ort, m_pSet);
11: DDX_FieldText(pDX, IDC_ECOUNTRY, m_pSet->m_Land, m_pSet);
12: DDX_FieldText(pDX, IDC_EEMAIL, m_pSet->m_EmailAdresse, m_pSet);
13: DDX_FieldText(pDX, IDC_EFAX, m_pSet->m_Faxnummer, m_pSet);
14: DDX_FieldText(pDX, IDC_EFNAME, m_pSet->m_Vorname, m_pSet);
15: DDX_FieldText(pDX, IDC_EHPHONE, m_pSet->m_TelefonPrivat, m_pSet);
16: DDX_FieldText(pDX, IDC_EID, m_pSet->m_Adressen_Nr, m_pSet);
17: DDX_FieldText(pDX, IDC_ELNAME, m_pSet->m_Nachname, m_pSet);
18: DDX_FieldText(pDX, IDC_ENOTES, m_pSet->m_Anmerkungen, m_pSet);
19: DDX_FieldText(pDX, IDC_ESNAME, m_pSet->m_EhepartnerName, m_pSet);
20: DDX_FieldText(pDX, IDC_ESTATE, m_pSet->m_Bundesland, m_pSet);
21: DDX_FieldText(pDX, IDC_EWEXT, m_pSet->m_Durchwahl_B_ro, m_pSet);
22: DDX_FieldText(pDX, IDC_EWPHONE, m_pSet->m_TelefonBeruflich, m_pSet);
23: DDX_FieldText(pDX, IDC_EZIP, m_pSet->m_Postleitzahl, m_pSet);
24: DDX_Text(pDX, IDC_EDOB, m_oledtDOB);
25: //}}AFX_DATA_MAP
26: // Geburtsdatum aus der Ansichtsvariablen zurück in den Recordset kopieren
27: if (pDX->m_bSaveAndValidate == TRUE)
28: m_pSet->m_Geburtsdatum = m_oledtDOB;
29: }
Zusätzlich zu den obigen Änderungen ist noch die Initialisierung der Variablen
m_Geburtsdatum
in der Recordset-Klasse zu entfernen. Diesen Code hat ebenfalls der
Anwendungs-Assistenten hinzugefügt. Auch hier müssen Sie die Regel durchbrechen,
den Code zu modifizieren, den Sie eigentlich niemals berühren sollten. Um diese Änderung
durchzuführen, können Sie einfach die Initialisierung dieser Variablen im Konstruktor
der Recordset-Klasse auskommentieren, wie es Zeile 19 von Listing 14.3
zeigt.
Listing 14.3: Der Konstruktor von CDbOdbcSet
1: CDbOdbcSet::CDbOdbcSet(CDatabase* pdb)
2: : CRecordset(pdb)
3: {
4: //{{AFX_FIELD_INIT(CDbOdbcSet)
5: m_Adressen_Nr = 0;
6: m_Vorname = _T("");
7: m_Nachname = _T("");
8: m_EhepartnerName = _T("");
9: m_Adresse = _T("");
10: m_Ort = _T("");
11: m_Bundesland = _T("");
12: m_Postleitzahl = _T("");
13: m_Land = _T("");
14: m_EmailAdresse = _T("");
15: m_TelefonPrivat = _T("");
16: m_TelefonBeruflich = _T("");
17: m_Durchwahl_B_ro = _T("");
18: m_Faxnummer = _T("");
19: //m_Geburtsdatum = 0;
20: m_SendenKarte = FALSE;
21: m_Anmerkungen = _T("");
22: m_nFields = 17;
23: //}}AFX_FIELD_INIT
24: m_nDefaultType = snapshot;
25: }
Jetzt können Sie die Anwendung erneut kompilieren und ausführen. Sie haben nun eine voll funktionsfähige Datenbankanwendung vor sich, die eine Menge von Datensätzen aus der Datenbank abruft und es ermöglicht, durch diese Datensätze zu blättern und Änderungen an den Daten vorzunehmen (siehe Abbildung 14.9).
Abbildung 14.9:
Die laufende Anwendung
Sie haben bereits eine voll funktionsfähige Datenbankanwendung erstellt, ohne eine
einzige Zeile Code zu schreiben. Allerdings fehlen noch ein paar Funktionen. In den
meisten Datenbankanwendungen kann der Benutzer neue Datensätze in die Datenbank
aufnehmen. Um einen neuen Datensatz in die Datenbank hinzuzufügen, müssen
Sie herausfinden, wie die nächste ID-Nummer lauten soll. Gehen Sie zum letzten Datensatz
im Recordset, um die ID zu ermitteln, und inkrementieren Sie diese um 1. Als
nächstes rufen Sie die Funktion AddNew
auf, um einen neuen Datensatz hinzuzufügen,
setzen das ID-Feld auf die neu berechnete ID und rufen dann die Funktion Update
auf,
um den neuen Datensatz zu speichern. Schließlich rufen Sie die Funktion Requery
auf,
um die Ergebnismenge zu aktualisieren, und gehen dann zum letzten Datensatz im Recordset,
um dem Benutzer die Eingabe von Daten in den neuen Datensatz anzubieten.
Um die beschriebene Funktionalität in der Beispielanwendung zu realisieren, fügen
Sie zunächst in die Recordset-Klasse eine Funktion ein, mit der Sie die nächste zu verwendende
ID-Nummer bestimmen. Nehmen Sie dazu eine Member-Funktion in die
Recordset-Klasse (CDbOdbcSet
) auf. Legen Sie den Funktionstyp als long
, die Funktionsdeklaration
mit GetMaxID
und den Zugriffsstatus als Public fest. Schreiben Sie den
Code gemäß Listing 14.4 in die Funktion.
Listing 14.4: Die Funktion GetMaxID der Klasse CDbOdbcSet
1: long CDbOdbcSet::GetMaxID()
2: {
3: // Zum letzten Datensatz gehen
4: MoveLast();
5: // ID dieses Datensatzes zurückgeben
6: return m_Adressen_Nr;
7: }
Als nächstes statten Sie das Menü Datensatz mit einem neuen Befehl aus, damit der Benutzer einen neuen Datensatz in die Datenbank aufnehmen kann. Konfigurieren Sie den neuen Menübefehl mit den Eigenschaften gemäß Tabelle 14.8.
Mit dem Klassen-Assistenten erstellen Sie eine Behandlungsroutine für die Nachricht
COMMAND
dieses Menüs in der Ansichtsklasse CDbOdbcView
. In die Funktion übernehmen
Sie den Code aus Listing 14.5.
Listing 14.5: Die Funktion OnRecordNew der Klasse CDbOdbcView
1: void CDbOdbcView::OnRecordNew()
2: {
3: // TODO: Code für Befehlsbehandlungsroutine hier einfügen
4: // Zeiger auf Recordset holen
5: CRecordset* pSet = OnGetRecordset();
6: // Sicherstellen, daß alle Änderungen am aktuellen
7: // Datensatz gespeichert wurden.
8: if (pSet->CanUpdate() && !pSet->IsDeleted())
9: {
10: pSet->Edit();
11: if (!UpdateData())
12: return;
13:
14: pSet->Update();
15: }
16: // ID für den neuen Datensatz ermitteln
17: long m_lNewID = m_pSet->GetMaxID() + 1;
18: // Den neuen Datensatz hinzufügen
19: m_pSet->AddNew();
20: // Die ID in den neuen Datensatz schreiben
21: m_pSet->m_Adressen_Nr = m_lNewID;
22: // Den neuen Datensatz speichern
23: m_pSet->Update();
24: // Den Recordset aktualisieren
25: m_pSet->Requery();
26: // Zum neuen Datensatz gehen
27: m_pSet->MoveLast();
28: // Formular aktualisieren
29: UpdateData(FALSE);
30: }
Korrespondierend zum Menübefehl Neuer Datensatz fügen Sie eine neue Schaltfläche in die Symbolleiste ein. Dann können Sie Ihre Anwendung kompilieren und ausführen. Nunmehr sollte es möglich sein, neue Datensätze in die Datenbank aufzunehmen, wobei Sie in die neuen Datensätze Daten eingeben können.
Als letztes ist noch die Funktionalität zum Löschen des aktuellen Datensatzes aus der
Datenbank zu implementieren. Um diese Aktion auszulösen, fügen Sie einen neuen
Menübefehl hinzu. Wenn der Benutzer diesen Befehl wählt, fragen Sie zur Sicherheit
nach, ob er den aktuellen Datensatz wirklich löschen möchte, und rufen dann die
Funktion Delete
auf, um den Datensatz zu entfernen. Anschließend rufen Sie die
Funktion MovePrev
auf, um zum vorherigen Datensatz im Recordset zu navigieren.
Diese Funktionalität realisieren Sie über einen neuen Menübefehl, den der Benutzer auswählen kann, um den aktuellen Datensatz aus der Datenbank zu löschen. Fügen Sie den neuen Befehl in das Menü Datensatz ein, und konfigurieren Sie den Befehl mit den Eigenschaften gemäß Tabelle 14.9.
Mit dem Klassen-Assistenten erstellen Sie eine Behandlungsroutine für die Nachricht
COMMAND
dieses Menübefehls in der Ansichtsklasse CDbOdbcView
. In die Funktion
schreiben Sie den Code aus Listing 14.6.
Listing 14.6: Die Funktion OnRecordDelete der Klasse CDbOdbcView
1: void CDbOdbcView::OnRecordDelete()
2: {
3: // TODO: Code für Befehlsbehandlungsroutine hier einfügen
4: // Fragen, ob Benutzer wirklich den Datensatz löschen will
5: if (MessageBox("Möchten Sie diesen Datensatz wirklich löschen?",
6: "Diesen Datensatz löschen?", MB_YESNO | MB_ICONQUESTION) == IDYES)
7: {
8: // Den Datensatz löschen
9: m_pSet->Delete();
10: // Zum vorherigen Datensatz gehen
11: m_pSet->MovePrev();
12: // Formular aktualisieren
13: UpdateData(FALSE);
14: }
15: }
In die Symbolleiste nehmen Sie eine neue Schaltfläche auf und verbinden sie mit der
Menü-ID IDM_RECORD_DELETE
, damit der Benutzer den aktuellen Datensatz löschen
kann, ohne erst über das Menü zu gehen. Wenn Sie jetzt Ihre Anwendung kompilieren
und ausführen, haben Sie eine voll funktionsfähige Datenbankanwendung vor
sich, in der Sie Datensätze hinzufügen, bearbeiten und löschen können (siehe Abbildung
14.10).
Abbildung 14.10:
Die fertiggestellte Anwendung
Die heutige Lektion hat gezeigt, wie man mit Hilfe der ODBC-Schnittstelle Datenbankanwendungen
erstellt, die sich in Verbindung mit einer beliebigen Datenbank einsetzen
lassen. Sie haben gesehen, wie die Klasse CRecordset
einen Großteil der Funktionalität
bereitstellt, so daß Sie Datenbankaktionen in Ihre Anwendungen einbauen
können. Weiterhin wurde erläutert, wie der Anwendungs-Assistent bereits einen wesentlichen
Teil der Datenbankfunktionalität realisiert, ohne daß Sie eine einzige Codezeile
schreiben müssen.
Morgen geht es um die neueste Technologie für den Datenbankzugriff von Microsoft, ActiveX Data Objects. Sie werden erfahren, wie man diese Technologie in Verbindung mit der ODBC-Schnittstelle einsetzt, um den Zugriff auf Datenbanken noch leichter zu realisieren.
Frage:
Warum soll ich mit der ODBC-Schnittstelle arbeiten und nicht mit den Datenzugriffsobjekten
(DAO)?
Antwort:
Die Datenzugriffsobjekte stützen sich auf die Microsoft Jet Database Engine,
um den gesamten Datenbankzugriff durchzuführen. Durch den zusätzlichen
Verwaltungsaufwand (Overhead) bläht sich Ihre Anwendung um mindestens
1 Mbyte auf. Falls Sie mit einer SQL-basierten Datenbank arbeiten, kann die
Datenbank bereits alle Aufgaben realisieren, die die Jet Engine ausführt. Darüber
hinaus verwendet die Jet Database Engine die ODBC-Schnittstelle, um
auf SQL-basierte Datenbanken zuzugreifen. Solange Sie also mit PC-basierten
Datenbanken wie Access, FoxPro oder Paradox arbeiten, erzielen Sie eine
bessere Leistungsbilanz, wenn Sie direkt in eigener Regie über die ODBC-
Schnittstelle arbeiten.
Frage:
Wie kann ich unterschiedliche Recordsets in eine MDI-Anwendung aufnehmen?
Antwort:
Mit dem Klassen-Assistenten können Sie zusätzliche, von CRecordset abgeleitete
Klassen in ein MDI-Anwendungsprojekt aufnehmen. Dazu legen Sie fest,
daß die neue Klasse eine MFC-Klasse ist und als Basisklasse die Klasse
CRecordset verwendet. Der Klassen-Assistent fordert die Angabe der Datenquelle,
genau wie Sie beim Anwendungs-Assistenten das Anwendungsgerüst
für die heutige Beispielanwendung erstellt haben. Nachdem Sie die Recordset-Klasse
erzeugt haben, können Sie eine neue Klasse auf die gleiche Weise
anlegen, wobei Sie CRecordView als Basisklasse spezifizieren. Wenn Sie auf
OK klicken, müssen Sie im Klassen-Assistenten festlegen, welche der Recordset-Klassen
mit der neuen Datensatzansichtsklasse zu verwenden ist.
2. Mit welchen Funktionen können Sie durch den Recordset in einem CRecordset
-
Objekt navigieren?
3. Welche Ansichtsklasse sollten Sie für Ihre ODBC-Anwendung wählen?
4. Welche Funktionssequenz ist aufzurufen, um einen neuen Datensatz in einen Recordset einzufügen?
5. Welche Funktion müssen Sie aufrufen, bevor sich Felder im CRecordset
-Objekt
mit allen Änderungen aktualisieren lassen?
Fügen Sie einen Menübefehl und ein Dialogfeld hinzu, um dem Benutzer die Möglichkeit zu geben, eine Datensatznummer als Ziel einzugeben. Verschieben Sie dann den Datensatzzeiger auf diesen Datensatz.