ODBC Programmierung unter MFC
Willemers Informatik-Ecke
Die MFC (Microsoft Foundation Class) kapselt die ODBC (Open Database Connectivity) in den Klassen CDatabase und CRecordset.

CDatabase

Öffnen und Schliessen

Nach der Erzeugung per Konstruktor, wird die Verbindung zur ODBC-Schnittstelle mit den Memberfunctions Open oder OpenEx hergestellt.

virtual BOOL Open( LPCTSTR strDataSourceName,
  BOOL bExclusive = FALSE,
  BOOL bReadOnly = FALSE,
  LPCTSTR lpszConnectString = "ODBC;",
  BOOL bUseCursorLib = TRUE );

strDataSourceName ist der Name, der für die Datenbank im ODBC genannt ist. Ist er NULL, wird er im ConnectString angegeben. Der ConnectString enthält DataSource, Username und Kennwort durch Semikolon getrennt z. B. , "DSN=MeineDatenbank;UID=Arnold;PWD=sagichnicht". Wird statt dieses Parameters 0 übergeben, erscheint ein Verbindungsdialog.

Alternativ (und empfohlen) ist der Aufruf OpenEx

virtual BOOL OpenEx( LPCTSTR lpszConnectString, DWORD dwOptions = 0 );

Der ConnectString enthält DataSource, Username und Kennwort durch Semikolon getrennt z. B. , "DSN=MeineDatenbank;UID=Arnold;PWD=sagichnicht". Wird statt dieses Parameters 0 übergeen, erscheint ein Verbindungsdialog.

Der Parameter Optionen kann die folgenden Werte (auch geodert) annehmen:

CDatabase::openExclusive nicht unterstützt
CDatabase::openReadOnly nur in der Datenbank lesen
CDatabase::useCursorLib Lade die ODBC Cursor Library DLL zur Verhinderung von DynaSets
CDatabase::noOdbcDialog zeige keinesfalls den Verbindungsdialog, auch wenn noch Informationen fehlen
CDatabase::forceOdbcDialog zeige immer den ODBC-Verbindungsdialog

Natürlich gibt es auch einen Close-Aufruf. Er hat keine Parameter und löst einen Rollback auf alle laufenden Aktionen und Transaktionen aus. Es empfielt sich, vor Schliessen der Database erst alle Recordsets zu schliessen.

Transaktionen

Mit der Funktion BOOL CDatabase::CanTransact() stellt man fest, ob der ODBC-Treiber überhaupt Transaktionen handhaben kann. Mit dem Aufruf von BOOL CDatabase::BeginTrans(); wird die Transaktion eingeleitet. Es empfielt sich, vor dem ersten Zugriff auf den Recordset BeginTrans aufzurufen. Die Transaktion wird abgeschlossen mit BOOL CDatabase::CommitTrans(); oder zurückgesetzt mit BOOL CDatabase::Rollback();.

Mit dem Aufruf void CDatabase::ExecuteSQL( LPCSTR lpszSQL ); können Datenbankkommandos abgesetzt werden, die keine Ergebnisse in Recordsets zurückliefern.

CRecordset

Der Recordset ist in zwei Ausprägungen vorhanden, dem Dynaset und Schnappschuss. Der Schnappschuss gibt den einmaligen, aktuellen Zustand der Datenbank wieder. Der Dynaset, der nicht von allen Treibern unterstützt wird, synchronisiert die Daten, die von anderen Anwendern verändert werden.

Typischerweise wird ein applikationseigener Recordset von CRecordset abgeleitet.

Erzeugen und Öffnen eines Recordsets

Der Kontruktor benötigt die Adresse der CDatabase-Variablen.
CRecordset::CRecordset( CDatabase* pDatabase = NULL);
Durch Open wird der Recordset mit einem SQL-Statement verbunden.

virtual BOOL Open(
   UINT nTyp = AFX_DB_USE_DEFAULT_TYPE,
   LPCTSTR strSQL = NULL,
   DWORD dwOptions = none );

Der Typ kann folgende Werte annehmen:

AFX_DB_USE_DEFAULT_TYPE
Der Defaultwert des Treibers
CRecordset::dynaset
Die Auswahl und Reihenfolge der Sätze erfolgt bei Eröffnung. Änderungen anderer Anwender werden bei jeder fetch-Operation sichtbar.
CRecordset::snapshot (default)
Die Auswahl und Reihenfolge der Sätze erfolgt bei Eröffnung. Änderungen anderer Anwender werden nur durch Schliessen und wieder Öffnen sichtbar.
CRecordset::dynamic
Die Auswahl und Reihenfolge der Sätze so wie auch Änderungen anderer Anwender werden bei jeder fetch-Operation sichtbar. Viele ODBC-Treiber unterstützen dies nicht.
CRecordset::forwardOnly
Ein Nur-Lese Recordset, der auch nur vorwärts lesen kann.

strSQL ist der Name einer Tabelle, ein SELECT-Statement (mit WHERE-Klausel) oder ein CALL-Statement für eine Stored-Procedure.

dwOptions kann eine oder mehrere der folgenden sein:

CRecordset::none
Der Recordset kann mit Edit, Delete und AddNew verändert werden.
CRecordset::appendOnly
Der Recordset darf nur durch AddNew und nicht per Edit oder Delete verändert werden.
CRecordset::readOnly
Recordset nur zum Lesen öffnen.
CRecordset::optimizeBulkAdd
Optimiert das Hinzufügen mehrerer Sätze bei Verwendung eines vorbereiteten SQL-Statement.
CRecordset::useMultiRowFetch
Damit können mehrere Zeilen in einer einzigen Fetch-Operation geladen werden.
CRecordset::skipDeletedRecords
Überspringt gelöschte Sätze, wenn durch den Recordset navigiert wird. Die Geschwindigkeit wird in bestimmten Fällen reduziert.
CRecordset::useBookmarks
Es werden Bookmarks verwendet, wenn der Treiber dies unterstützt.
CRecordset::noDirtyFieldCheck
Schaltet das automatische Dirty Field Checking (double buffering)aus.
CRecordset::executeDirect
Verwendet keine vorbereiteten SQL-Statements.
CRecordset::useExtendedFetch
Implementiert SQLExtendedFetch statt SQLFetch.
CRecordset::userAllocMultiRowBuffers
Der Anwender wird den Speicher für die Daten alloziieren.

Update eines Recordset

Zunächst wird mit den Befehlen AddNew bzw Edit die Änderung eingeleitet. Die Felder werden besetzt und anschliessend mit Update bestätigt.

AddNew()
AddNew erzeugt einen neuen, leeren Satz und verwendet dazu die Felder des Recordsets. Die Datenfelder werden belegt und per Update in die Datenquelle geschrieben.
Edit()
Mit Edit werden Änderungen im aktuellen Satz eingeleitet. Die Recordset-Variablen werden direkt geändert. Durch den Aufruf von Update gehen die &Aunderungen an die Datenquelle.
Delete()
Löscht den aktuellen Datensatz.

Navigieren

void MoveFirst( );
Durch diesen Aufruf wird der erste Satz des Recordset der aktuelle Satz. Es ist nicht erforderlich MoveFirst direkt nach dem Öffnen des Recordset zu rufen.
void MoveLast( );
Sofern nicht bulk-fetching gesetzt ist, wird der letzte Satz im Recordset der aktuelle.
void MoveNext( );
wechsele den aktuellen Satz zum nächsten im Recordset. Gelöschte Sätze werden nicht unbedingt übersprungen. Die IsDeleted()-Funktion sollte benutzt werden.
void MovePrev( );
wechsele den aktuellen Satz zum vorherigen im Recordset.

Zugriff auf die Felder

void GetFieldValue( LPCTSTR lpszName, CDBVariant& varValue, short nFieldType = DEFAULT_FIELD_TYPE );
void GetFieldValue( short nIndex, CDBVariant& varValue, short nFieldType = DEFAULT_FIELD_TYPE );
void GetFieldValue( LPCTSTR lpszName, CString& strValue );
void GetFieldValue( short nIndex, CString& strValue );

Parameter

lpszName
Feldname
varValue
Eine Referenz auf ein CDBVariant-Objekt, das den Feldwert hält
nFieldType
Der ODBC C-Datentyp des Feldes. Bei Angabe von DEFAULT_FIELD_TYPE wird der C-Typ aus dem SQL Datentyp nach der folgenden Tabelle ermittelt.
nIndex
Der Feldindex (nullbasierend)
strValue
Referenz auf ein CString, das den Feldinhalt als String enthält, ganz gleich, welchen Datentyp das Feld hat.
Beispiel:

   CDatabase db;
   db.OpenEx( NULL, CDatabase::forceOdbcDialog );

   CRecordset rs( &db );
   rs.Open( CRecordset::forwardOnly, _T( "SELECT * FROM MeineTabelle" ) );

   CDBVariant Wert;

   int n = rs.GetODBCFieldCount( );
   while( !rs.IsEOF() ) {
      for( int i = 0; i < n; i++ ) {
         rs.GetFieldValue( i, Wert );
	 . . . 
      }
      rs.MoveNext( );
   }
   rs.Close( );
   db.Close( );

Informationen zu den Feldern

void CRecordset::GetODBCFieldInfo( LPCTSTR lpszName, CODBCFieldInfo& fieldinfo );
void CRecordset::GetODBCFieldInfo( short nIndex, CODBCFieldInfo& fieldinfo );

lpszName
Der Feldname
fieldinfo
Ein Zeiger auf eine CODBCFieldInfo Struktur
nIndex
Feldindex (nullbasierend)
Die Informationen über die Felder findet sich in der Struktur CODBCFieldInfo

Die CODBCFieldInfo Struktur
struct CODBCFieldInfo {
   CString m_strName;
   SWORD m_nSQLType;
   UDWORD m_nPrecision;
   SWORD m_nScale;
   SWORD m_nNullability;
};
m_strName
Feldname
m_nSQLType
Der SQL-Datentyp des Feldes.
m_nPrecision
Die maximale Genauigkeit des Feldes.
m_nScale
Die Grösse des Feldes.
m_nNullability
Gibt an, ob das Feld Nullwerte akzeptiert (SQL_NULLABLE) oder nicht (SQL_NO_NULLS).

Ableitung der Klasse CRecordset

Das folgende ist eine (gekürzte) Deklaration, die der AppWizard erzeugt, wenn er auf einer bestehenden ODBC-Datenbank aufsetzt.
class COdbcSet : public CRecordset
{
public:
	COdbcSet(CDatabase* pDatabase = NULL);

// Field/Param Data
	//{{AFX_FIELD(COdbcSet, CRecordset)
	CString	m_RefNr;
	CTime	m_ChangeTimeStamp;
	long	m_PosCnt;
	CString	m_AManufacturer;
	CString	m_AType;
	CString	m_ASerNr;
	//}}AFX_FIELD

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(COdbcSet)
	public:
	virtual CString GetDefaultConnect();	// Default connection string
	virtual CString GetDefaultSQL(); 	// default SQL for Recordset
	virtual void DoFieldExchange(CFieldExchange* pFX);	// RFX support
	//}}AFX_VIRTUAL
};
Die abgeleitete Klasse enthält für jedes Tabellenfeld eine Variable, über die auf die Felder zugegriffen werden kann. Die Funktion DoFieldExchange, die ebenfalls vom AppWizard ereugt wird, sorgt für den Abgleich zwischen Datenbank und Speicher (beispielsweise bei Update()).