TN053: Custom DFX Routines for DAO Database Classes
As of Visual C++ .NET, the Visual C++ environment and wizards no longer support DAO (although the DAO classes are included and you can still use them). Microsoft recommends that you use OLE DB Templates or ODBC and MFC for new projects. You should only use DAO in maintaining existing applications.
This technical note describes the DAO record field exchange (DFX) mechanism. To help understand what is happening in the DFX routines, the DFX_Text function will be explained in detail as an example. As an additional source of information to this technical note, you can examine the code for the other the individual DFX functions. You probably will not need a custom DFX routine as often as you might need a custom RFX routine (used with ODBC database classes).
This technical note contains:
Examples using DAO Record Field Exchange and Dynamic Binding
The DAO record field exchange mechanism (DFX) is used to simplify the procedure of retrieving and updating data when using the CDaoRecordset class. The process is simplified using data members of the CDaoRecordset class. By deriving from CDaoRecordset, you can add data members to the derived class representing each field in a table or query. This "static binding" mechanism is simple, but it may not be the data fetch/update method of choice for all applications. DFX retrieves every bound field each time the current record is changed. If you are developing a performance-sensitive application that does not require fetching every field when currency is changed, "dynamic binding" via CDaoRecordset::GetFieldValue and CDaoRecordset::SetFieldValue may be the data access method of choice.
DFX and dynamic binding are not mutually exclusive, so a hybrid use of static and dynamic binding can be used.
Example 1 — Use of DAO Record Field Exchange only
(assumes CDaoRecordset — derived class CMySet already open)
// Add a new record to the customers table myset.AddNew(); myset.m_strCustID = _T("MSFT"); myset.m_strCustName = _T("Microsoft"); myset.Update();
Example 2 — Use of dynamic binding only
(assumes using CDaoRecordset class, rs, and it is already open)
// Add a new record to the customers table COleVariant varFieldValue1 ( _T("MSFT"), VT_BSTRT ); //Note: VT_BSTRT flags string type as ANSI, instead of UNICODE default COleVariant varFieldValue2 (_T("Microsoft"), VT_BSTRT ); rs.AddNew(); rs.SetFieldValue(_T("Customer_ID"), varFieldValue1); rs.SetFieldValue(_T("Customer_Name"), varFieldValue2); rs.Update();
Example 3 — Use of DAO Record Field Exchange and dynamic binding
(assumes browsing employee data with CDaoRecordset-derived class emp)
// Get the employee's data so that it can be displayed emp.MoveNext(); // If user wants to see employee's photograph, // fetch it COleVariant varPhoto; if (bSeePicture) emp.GetFieldValue(_T("photo"), varPhoto); // Display the data PopUpEmployeeData(emp.m_strFirstName, emp.m_strLastName, varPhoto);
How DFX Works
The DFX mechanism works in a similar fashion to the record field exchange (RFX) mechanism used by the MFC ODBC classes. The principles of DFX and RFX are the same but there are numerous internal differences. The design of the DFX functions was such that virtually all the code is shared by the individual DFX routines. At the highest level DFX only does a few things.
DFX constructs the SQL SELECT clause and SQL PARAMETERS clause if necessary.
DFX constructs the binding structure used by DAO's GetRows function (more on this later).
DFX manages the data buffer used to detect dirty fields (if double-buffering is being used)
DFX manages the NULL and DIRTY status arrays and sets values if necessary on updates.
At the heart of the DFX mechanism is the CDaoRecordset derived class's DoFieldExchange function. This function dispatches calls to the individual DFX functions of an appropriate operation type. Before calling DoFieldExchange the internal MFC functions set the operation type. The following list shows the various operation types and a brief description.
Builds PARAMETERS clause
Builds SELECT clause
Sets up binding structure
Sets parameter values
Sets NULL status
Allocates cache for dirty check
Saves current record to cache
Restores cache to member values
Sets field status & value to NULL
Marks fields dirty if not PSEUDO NULL
Marks fields dirty if don't match cache
Sets field values marked as dirty
In the next section, each operation will be explained in more detail for DFX_Text.
The most important feature to understand about the DAO record field exchange process is that it uses the GetRows function of the CDaoRecordset object. The DAO GetRows function can work in several ways. This technical note will only briefly describe GetRows as it is outside of the scope of this technical note.
DAO GetRows can work in several ways.
It can fetch multiple records and multiple fields of data at one time. This allows for faster data access with the complication of dealing with a large data structure and the appropriate offsets to each field and for each record of data in the structure. MFC does not take advantage of this multiple record fetching mechanism.
Another way GetRows can work is to allow programmers to specify binding addresses for the retrieved data of each field for one record of data.
DAO will also "call back" into the caller for variable length columns in order to allow the caller to allocate memory. This second feature has the benefit of minimizing the number of copies of data as well as allowing direct storage of data into members of a class (the CDaoRecordset derived class). This second mechanism is the method MFC uses to bind to data members in CDaoRecordset derived classes.
It is apparent from this discussion that the most important operation implemented in any DFX function must be the ability to set up the required data structures to successfully call GetRows. There are a number of other operations that a DFX function must support as well, but none as important or complex as correctly preparing for the GetRows call.
The use of DFX is described in the online documentation. Essentially, there are two requirements. First, members must be added to the CDaoRecordset derived class for each bound field and parameter. Following this CDaoRecordset::DoFieldExchange should be overridden. Note that the data type of the member is important. It should match the data from the field in the database or at least be convertible to that type. For example a numeric field in database, such as a long integer, can always be converted to text and bound to a CString member, but a text field in a database may not necessarily be converted to a numeric representation, such as long integer and bound to a long integer member. DAO and the Microsoft Jet database engine are responsible for the conversion (rather than MFC).
As mentioned previously, the best way to explain how DFX works is to work through an example. For this purpose going through the internals of DFX_Text should work quite well to help provide at least a basic understanding of DFX.
All the remaining operations only deal with using the data cache. The data cache is an extra buffer of the data in the current record that is used to make certain things simpler. For instance, "dirty" fields can be automatically detected. As described in the online documentation it can be turned off completely or at the field level. The implementation of the buffer utilizes a map. This map is used to match up dynamically allocated copies of the data with the address of the "bound" field (or CDaoRecordset derived data member).
Model your custom DFX routines on the existing DFX routines for standard data types.