The MFC ODBC Classes—CRecordset and CDatabase

With the MFC classes for Windows, you use C++ objects instead of window handles and device context handles; with the MFC ODBC classes, you use objects instead of connection handles and statement handles. The environment handle is stored in a global variable and is not represented by a C++ object. The two principal ODBC classes are CDatabase and CRecordset. Objects of class CDatabase represent ODBC connections to data sources, and objects of class CRecordset represent scrollable rowsets. The Visual C++ documentation uses the term "recordset" instead of "rowset" to be consistent with Microsoft Visual Basic and Microsoft Access. You seldom derive classes from CDatabase, but you generally derive classes from CRecordset to match the columns in your database tables.

For the author query in the previous section, you would derive (with the help of ClassWizard) a CAuthorSet class from CRecordset that had data members for first name, last name, city, state, and zip code. Your program would construct a CAuthorSet object (typically embedded in the document) and call its inherited Open member function. Using the values of parameters and data members, CRecordset::Open constructs and opens a CDatabase object; this function issues an SQL SELECT statement and then moves to the first record. Your program would then call other CRecordset member functions to position the ODBC cursor and exchange data between the database fields and the CAuthorSet data members. When the CAuthorSet object is deleted, the recordset is closed and, under certain conditions, the database is closed and deleted. Figure 31-2 shows the relationships between the C++ objects and the ODBC components.

Click to view at full size.

Figure 31-2. MFC ODBC class database relationships.

It's important to recognize that the CAuthorSet object contains data members that represent only one row in a table, the so-called "current record." The CRecordset class, together with the underlying ODBC rowset code, manages the database dynaset or snapshot.

It's possible to have several active dynasets or snapshots per data source, and you can use multiple data sources within the same program.

The important CRecordset member functions discussed in this chapter are summarized in the table below.
FunctionDescription
OpenOpens the recordset
AddNewPrepares to add a new record to the table
UpdateCompletes an AddNew or Edit operation by saving the new or edited data in the data source
DeleteDeletes the current record from the recordset
EditPrepares to implement changes on the current record
IsBOFDetermines whether the recordset has been positioned before the first record
IsEOFDetermines whether the recordset has been positioned after the last record
MoveNextSets the current record to the next record or to the next rowset
MoveFirstSets the current record to the first record in the recordset
MoveLastSets the current record to the last record or to the last rowset
MovePrevSets the current record to the previous record or to the previous rowset
GetDefaultConnectGets the default connect string for the data source on which the recordset is based
GetDefaultSQLGets the default SQL string
DoFieldExchangeExchanges data between the recordset data fields and the corresponding record on the data source
GetStatusGets the index of the current record in the recordset and the final count status
GetRecordCountDetermines the highest-numbered record yet encountered as the user moves through the records
GetODBCFieldCountGets the number of fields in the recordset object
GetODBCFieldInfoGets information about the fields in the recordset

Counting the Rows in a Recordset

It's difficult to know how many records are contained in an ODBC recordset. ODBC doesn't provide an accurate count of the rows in a recordset until you've read past the end. Until that time, the count returned from the CRecordset::GetRecordCount member function is a "high-water mark" that returns only the last row accessed by CRecordset::MoveNext. The CRecordset::GetStatus function returns a CRecordsetStatus object, which has a member m_bRecordCountFinal that indicates whether the count is final.

The CRecordset::MoveLast function does not register the record count for you, even for dynasets. If you want to know how many records are included in a recordset, loop through the whole table with MoveNext calls. (A faster alternative is to use the COUNT function.) If your program adds or deletes a record or if another user adds or deletes a record, the record count is not adjusted.

Processing ODBC Exceptions

Many MFC ODBC calls don't return an error code but instead throw a CDBException object, which contains a string describing the error. Suppose you are trying to delete a record from a table in an Access database. Access might be enforcing referential integrity rules, which means that you're not allowed to delete that row because a row in another table depends on it. If you call CRecordset::Delete, you'll see an ODBC error message box that came from the MFC base classes.

You certainly appreciate the error message, but now ODBC has "lost its place" in the recordset, and there is no longer a current record. Your program needs to detect the error so that it won't call functions that depend on a current record, such as CRecordset::MoveNext. You must handle the exception in this way:

try {
    m_pSet->Delete();
}
catch(CDBException* e) {
    AfxMessageBox(e->m_strError);
    e->Delete();
    m_pSet->MoveFirst(); // lost our place!
    UpdateData(FALSE);
    return;
}
m_pSet->MoveNext();