Teach Yourself Database Programming
with Visual C++ 6 in 21 days


Day 15
      The ODBC API and the MFC ODBC Classes



Yesterday, you explored the ODBC and DAO APIs and were introduced to the MFC wrapper classes for both. Today, you will create a simple application using the wrapper classes and discuss the data binding that takes place. You will also look at what the API is doing through the use of the wrapper classes.

Today you will

The Address Book

This chapter focuses on using the wrapper classes that MFC provides for the ODBC and DAO APIs. You do this by creating a simple application to list addresses for your friends, like the one in Figure 15.1. The database that you will be using was created with Access 97 and will contain a single table (Addresses). This table will contain the fields shown in Figure 15.1:

Figure 15.1 : The Address book table (Addresses) structure.

To simplify things, you examine the application development code, using the MFC AppWizard for creating your data bindings. After you do this, you will take a closer look at how the table data can be manually bound to the application's variables.

Using the MFC ODBC Wrapper Classes

Day 14, "Legacy Database APIs," provides an introduction to the ODBC API and to the MFC wrappers for the API. One important item that Day 14 doesn't discuss is the CRecordView class. This class doesn't really wrap the API so much as it creates a go-between data-binding class that fits into the MFC's Document/View architecture. CRecordView is derived from the CFormView class, which ultimately is derived from CView. The view classes provide the user interface mechanisms to display the data and handle Windows messages. The CRecordSet and CDatabase classes are closely coupled with CRecordView, as you will see in your application code. It contains a member variable to handle the actual data cursor. It does this in the form of a record pointer. If you have had the opportunity to program database applications the hard way, you will definitely appreciate this class. The CRecordView also contains a pointer to the CRecordSet class.

NOTE
If you are unfamiliar with MFC and the MFC Document/View architecture, the following sections might appear to be a broad step from the explanation of the API to the actual implementation of the application. This chapter does this to highlight certain aspects of implementing database applications; however, it does so using the most widely used approach.

Let's jump right in and create the sample application.

Creating the Application

Before the AppWizard creates the application, the database and ODBC data source must exist. You can use the Control Panel ODBC applet to determine whether a database and ODBC data source exist for the targeted application.

NOTE
The application code is included on the accompanying CD-ROM, which includes the AddressBook database.

From the File menu, select New to start the MFC AppWizard. The screen shown in Figure 15.2 appears. Based on your system file structure and where your database and application files reside, fill in the appropriate information shown in Figure 15.2. In this case, you are creating an executable that will be MFC-based.

Figure 15.2 : MFC AppWizard Step 1: Creating the application.

Figure 15.3 shows the screen where you specify the database support for your application. At this point, it is asking whether you want database support. (You can choose to skip this selection, by choosing None, and then put it in later.) Select Database View Without File Support. After you select the database support, the next step is to set the data source. For your example, select ODBC as shown in Figure 15.3.

Figure 15.3 : MFC AppWizard Step 2: Defining database requirements.

You then select the table(s) that you would like to have support for.

The AppWizard will build the data relationships for the recordset that relates to the tables you select. For every table that you select, the wizard will create a CRecordSet-based class that wraps the table. You'll come back to this when you look at the code. Figure 15.4 shows the classes that AppWizard will create.

Figure 15.4 : MFC AppWizard Step 6: Files created.

You only need to concern yourself with the CAddressBookODBCView class and the CAddressBookODBCSet class. These two classes contain the information that will help you understand how the data is passed from the data source to the application. Listing 15.1 is the definition file for the CAddressBookODBCSet class.


Listing 15.1  The CADDRESSBOOKODBCSET Class Declaration

 1:  // AddressBookODBCSet.h : interface of the 
 2:  // CAddressBookODBCSet class
 3:  ///////////////////////////////////////////////////////
 4:
 5:  #if !defined(AFX_ADDRESSBOOKODBCSET_H__D21600E1_4140_
          11D2_9D78_000000000000__INCLUDED_)
 6:  #define AFX_ADDRESSBOOKODBCSET_H__D21600E1_
          4140_11D2_9D78_000000000000__INCLUDED_
 7:
 8:  #if _MSC_VER >= 1000
 9:  #pragma once
10:  #endif // _MSC_VER >= 1000
11:
12:  class CAddressBookODBCSet : public CRecordset
13:  {
14:  public:
15:    CAddressBookODBCSet(CDatabase* pDatabase = NULL);
16:    DECLARE_DYNAMIC(CAddressBookODBCSet)
17:
18:    // Field/Param Data
19:    //{{AFX_FIELD(CAddressBookODBCSet, CRecordset)
20;    long  m_ID;
21:    CString  m_Last_Name;
22:    CString  m_First_Name;
23:    CString  m_Street;
24:    CString  m_City;
25:    CString  m_State;
26:    long  m_Zip;
27:    CString  m_Phone;
28:    //}}AFX_FIELD
29:
30:    // Overrides
31:    // ClassWizard generated virtual function overrides
32:    //{{AFX_VIRTUAL(CAddressBookODBCSet)
33:    public:
34:    virtual CString GetDefaultConnect();  // Default connection string
35:    virtual CString GetDefaultSQL();   // default SQL for Recordset
36:    virtual void DoFieldExchange(CFieldExchange* pFX);  // RFX support
37:    //}}AFX_VIRTUAL
38:
39:    // Implementation
40:    #ifdef _DEBUG
41:      virtual void AssertValid() const;
42:      virtual void Dump(CDumpContext& dc) const;
43:    #endif
44:
45:  };
46:
47:  //{{AFX_INSERT_LOCATION}}
48:  // Microsoft Developer Studio will insert additional 
49:  // declarations immediately before the preceding line.
50:
51:  #endif // !defined(AFX_ADDRESSBOOKODBCSET_H__D21600E1_
          4140_11D2_9D78_000000000000__INCLUDED_)

Notice that line 19 is where the data mapping starts. The AFX_FIELD declaration indicates that this is field data for the recordset. Just think of the number of SQLBindColumn calls that you would have to make to declare each field if the table were very large.

The MFC DDX/DDV message-handling mechanism defines the connection between the table column data and the applications variable used to present that data. The other part of the binding that takes place here is the actual data attribute definition. The CString class is a wrapper for a C character string. The CString class is mapped to the character data represented in the columns. The long is mapped to the data type for the Zip code in the Access database. In this case, it is defined as an integer.

Let's take a look at the CRecordView-based class. Here you see the implementation file for the CAddressBookODBCView class in Listing 15.2.


Listing 15.2  The CADDRESSBOOKODBCVIEW Class Declaration

 1:    // AddressBookODBCView.h : interface of the 
 2:    // CAddressBookODBCView class
 3:    /////////////////////////////////////////////////////
 4:
 5:    #if !defined(AFX_ADDRESSBOOKODBCVIEW_H__D21600DF_4140_
            11D2_9D78_000000000000__INCLUDED_)
 6:    #define AFX_ADDRESSBOOKODBCVIEW_H__D21600DF_4140_11D2_
            9D78_000000000000__INCLUDED_
 7:
 8:    #if _MSC_VER >= 1000
 9:    #pragma once
10:    #endif // _MSC_VER >= 1000
11:
12:    class CAddressBookODBCSet;
13:
14:    class CAddressBookODBCView : public CRecordView
15:    {
16:    protected: // create from serialization only
17:        CAddressBookODBCView();
18:        DECLARE_DYNCREATE(CAddressBookODBCView)
19:    
20:    public:
21:        //{{AFX_DATA(CAddressBookODBCView)
22:        enum{ IDD = IDD_ADDRESSBOOKODBC_FORM };
23:        CAddressBookODBCSet* m_pSet;
24:            // NOTE: the ClassWizard will add data members here
25:        //}}AFX_DATA
26:
27:    // Attributes
28:    public:
29:        CAddressBookODBCDoc* GetDocument();
30:
31:    // Operations
32:    public:
33:
34:    // Overrides
35:        // ClassWizard generated virtual function overrides
36:        //{{AFX_VIRTUAL(CAddressBookODBCView)
37:        public: 
38:        virtual CRecordset* OnGetRecordset();
39:        virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
40:        protected:
41:        virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV // support
42:        virtual void OnInitialUpdate(); // called first time after 
           // construct
43:        virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
44:        virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
45:        virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
46:        //}}AFX_VIRTUAL
47:
48:    // Implementation
49:    public:
50:        virtual ~CAddressBookODBCView();
51:    #ifdef _DEBUG
52:        virtual void AssertValid() const;
53:        virtual void Dump(CDumpContext& dc) const;
54:    #endif
55:
56:    protected:
57:
58:    // Generated message map functions
59:    protected:
60:        //{{AFX_MSG(CAddressBookODBCView)
61:            // NOTE - The ClassWizard will add and remove member 
               // functions here.
62:            //    DO NOT EDIT what you see in these blocks of generated //    code!
63:        //}}AFX_MSG
64:        DECLARE_MESSAGE_MAP()
65:    };
66:
67:    #ifndef _DEBUG  // debug version in AddressBookODBCView.cpp
68:    inline CAddressBookODBCDoc* CAddressBookODBCView::GetDocument()
69:       { return (CAddressBookODBCDoc*)m_pDocument; }
70:    #endif
71:
72:    ///////////////////////////////////////////////////////////////////
73:
74:    //{{AFX_INSERT_LOCATION}}
75:    // Microsoft Developer Studio will insert additional declarations 
76:    // immediately before the preceding line.
77:
78:    #endif // !defined(AFX_ADDRESSBOOKODBCVIEW_H__D21600DF_4140_
            11D2_9D78_000000000000__INCLUDED_)

The CAddressBookODBCView class contains quite a few more function members, but most are related to the Document/View architecture of MFC. Notice on line 23 that you have a pointer to the CAddressBookODBCSet class in the CAddressBookODBCView class. This pointer is the primary link between the API wrapping and the Document/View architecture. The CAddressBookODBCView class contains a document that is the application's data keeper. Data is passed between the view and dialog classes and the GUI via the MFC DDX/DDV data exchange mechanisms. The view gets access to the document directly via GetDocument; the document pumps notifications (and hints) to the view via the CDocument::UpdateAllViews/CView;;OnUpdate coupling. For data to be bound from a database to the application, the application's view must know about the data structure. The m_pSet pointer is the mechanism that accomplishes this. Let's take a look at what the Document class contains. Notice that the document contains the RecordSet (line 21).

Something very interesting happens in Listing 15.3. By looking at the wrapper declarations for your application, you have learned how the data binding takes place. The document class represents the application's memory. The view class is your window into that data. But you are working with databases, and the data in this case is stored on disk. The RecordSet is the API wrapper that will provide the link to the data in the database. By containing the recordset, the document class essentially maps the data into the application's memory.


Listing 15.3  The CADDRESSBOOKODBCDOC Class Declaration

 1:    // AddressBookODBCDoc.h : interface of the CAddressBookODBCDoc 
       // class
 2:    //
 3:    ///////////////////////////////////////////////////////////////////
 4:
 5:    #if !defined(AFX_ADDRESSBOOKODBCDOC_H__D21600DD_4140_11D2_9D78_
            000000000000__INCLUDED_)
 6:    #define AFX_ADDRESSBOOKODBCDOC_H__D21600DD_4140_11D2_9D78_
            000000000000__INCLUDED_
 7:
 8:    #if _MSC_VER >= 1000
 9:    #pragma once
10:    #endif // _MSC_VER >= 1000
11:
12:
13:    class CAddressBookODBCDoc : public CDocument
14:    {
15:    protected: // create from serialization only
16:        CAddressBookODBCDoc();
17:        DECLARE_DYNCREATE(CAddressBookODBCDoc)
18:
19:    // Attributes
20:    public:
21:        CAddressBookODBCSet m_addressBookODBCSet;
22:
23:    // Operations
24:    public:
25:
26:    // Overrides
27:        // ClassWizard generated virtual function overrides
28:        //{{AFX_VIRTUAL(CAddressBookODBCDoc)
29:        public:
30:        virtual BOOL OnNewDocument();
31:        //}}AFX_VIRTUAL
32:
33:    // Implementation
34:    public:
35:        virtual ~CAddressBookODBCDoc();
36:    #ifdef _DEBUG
37:        virtual void AssertValid() const;
38:        virtual void Dump(CDumpContext& dc) const;
39:    #endif
40:
41:    protected:
42:
43:    // Generated message map functions
44:    protected:
45:        //{{AFX_MSG(CAddressBookODBCDoc)
46:            // NOTE - The ClassWizard will add and remove member 
47:            // functions here. DO NOT EDIT what you see in these 
48             // blocks of generated code !
49:        //}}AFX_MSG
50:        DECLARE_MESSAGE_MAP()
51:    };
52:
53:    ///////////////////////////////////////////////////////////////////
54:
55: //{{AFX_INSERT_LOCATION}}
56: // Microsoft Developer Studio will insert additional declarations 
57: // immediately before the previous 57: line.
58:
59:    #endif // !defined(AFX_ADDRESSBOOKODBCDOC_H__D21600DD_4140_11D2_
            9D78_000000000000__INCLUDED_)

You have now created an application that contains the ODBC wrappers that you will need to interface to your database. However, the AppWizard really can't determine what you plan to do with the data. If the application were to be built and executed at this point, you would see something similar to Figure 15.5.

Figure 15.5 : The Address Book ODBC application. First run.

For the application to do any real work, you first have to create controls for the data to be displayed in.

Getting Data

Look at Listing 15.4 to see how much the AppWizard really does for you.


Listing 15.4  The ONINITIALUPDATE Implementation

 1:    void CAddressBookODBCView::OnInitialUpdate()
 2:    {
 3:        m_pSet = &GetDocument()->m_addressBookODBCSet;
 4:        CRecordView::OnInitialUpdate();
 5:    }

The OnInitialUpdate function member of your view class will set your recordset pointer. Notice that it gets the RecordSet information from the document-remember our discussion about the Document/View and how the RecordView tied everything together.

In Listing 15.5, the two functions GetDefaultConnect() and GetDefaultSQL() are used. The GetDefaultConnect function member will provide the data source information that you need to pass to the database open command. The GetDefaultSQL function member will pass back the defining information for your fetch routines. (In this case, the SQL statement that queries the Addresses table).


Listing 15.5  The GETDEFAULTCONNECT and GETDEFAULTSQL Implementations

 1:    CString CAddressBookODBCSet::GetDefaultConnect()
 2:    {
 3:        return _T("ODBC;DSN=AddressBook");
 4:    }
 5:
 6:    CString CAddressBookODBCSet::GetDefaultSQL()
 7:    {
 8:        return _T("[Addresses]");
 9:    }
10:
11:    void CAddressBookODBCSet::DoFieldExchange(CFieldExchange* pFX)
12:    {
13:        //{{AFX_FIELD_MAP(CAddressBookODBCSet)
14:        pFX->SetFieldType(CFieldExchange::outputColumn);
15:        RFX_Long(pFX, _T("[ID]"), m_ID);
16:        RFX_Text(pFX, _T("[Last Name]"), m_Last_Name);
17:        RFX_Text(pFX, _T("[First Name]"), m_First_Name);
18:        RFX_Text(pFX, _T("[Street]"), m_Street);
19:        RFX_Text(pFX, _T("[City]"), m_City);
20:        RFX_Text(pFX, _T("[State]"), m_State);
21:        RFX_Long(pFX, _T("[Zip]"), m_Zip);
22:        RFX_Text(pFX, _T("[Phone]"), m_Phone);
23:        //}}AFX_FIELD_MAP
24:    }

In Listing 15.6, notice line 4 from the modified OnInitialUpdate function. To open the recordset, simply call the Open function member of the recordset and pass the SQL string. This isn't always necessary. The GetDefaultSQL function member contains a shorthand version of the statement listed previously. If the SELECT statement needs to change, simply insert the SQL statement in the recordset's OPEN function.


Listing 15.6  The Modified ONINITIALUPDATE Implementation

 1:    void CAddressBookODBCView::OnInitialUpdate()
 2:    {
 3:        m_pSet = &GetDocument()->m_addressBookODBCSet;
 4:        m_pSet->Open(CRecordSet::snapshot,"SELECT * FROM Addresses");
 5:        CRecordView::OnInitialUpdate();
 6:    }

NOTE
It is important to note here that the OnInitialUpdate routine will actually perform the connecting and opening of the data source. It then will set the pointer to the recordset.

Updating the Application's Variables

By using the AppWizard to generate this application, you didn't use the CDatabase class to connect or open the data source. By letting AppWizard define the recordset parameters, the code generated gets around this. If the support selected during the AppWizard phase does not include this support, the application developer would have to add a CDatabase class and CRecordSet class implementation in the CRecordView. More work, but essentially the same result.

What you haven't learned is how the table data is transferred to the application's data. Inside MFC database support is the Record Field Exchange (RFX) mechanism. Similar to Dynamic Data Exchange (DDX), the RFX layer performs the underlying field-to-data transfers. This is done at the message interface level.

You have now created an application that uses the ODBC API. You will next explore the DAO API.

Using the MFC DAO Wrapper Classes

The steps needed to create the DAO application are similar to the steps needed to create an MFC ODBC application. However, there are some minor differences. When asked for database support (refer to Figure 15.3), select Database Support with File Support (the last selection). Remember the discussion in Day 14 concerning the two APIs. ODBC uses a data source, which can be network-resident and doesn't rely on the file system. DAO uses the Jet engine, which is file-based. After this selection is made, the steps are identical to those for the ODBC application.

Many applications that you might run across will obviously have more than one recordset defined to accept a query on just one table. The AppWizard gives the application developer a starting point. As the application is created, notice that the files produced by the AppWizard are almost identical to those created when you built the ODBC application. The MFC wrapper classes for the DAO API were designed to closely match those created for the ODBC API, even though the APIs are completely different. Taking a good look under the hood is beyond the scope of this book, but if you have the opportunity, look at the MFC wrapper code for the DAO API classes that AppWizard creates. If you compare the implementation of the CDaoRecordView class and the CRecordView class, you will also see the similarities. Remember that the view is the coupling class to the MFC Document/View architecture. Most of the differences will appear at the recordset class wrappers.

Taking a Closer Look

Let's take a look at in Listing 15.7 the class implementations that the AppWizard generated for us. First, look at the Record Set class.


Listing 15.7  The DAO CADDRESSBOOKDAOSET Class Declaration

 1:    // AddressBookDAOSet.h : interface of the CAddressBookDAOSet class
 2:    //
 3:    ///////////////////////////////////////////////////////////////////
 4:
 5:    #if !defined(AFX_ADDRESSBOOKDAOSET_H__990F28BF_41F8_11D2_9D79_
            000000000000__INCLUDED_)
 6:    #define AFX_ADDRESSBOOKDAOSET_H__990F28BF_41F8_11D2_9D79_
            000000000000__INCLUDED_
 7:
 8:    #if _MSC_VER >= 1000
 9:    #pragma once
10:    #endif // _MSC_VER >= 1000
11:
12:    class CAddressBookDAOSet : public CDaoRecordset
13:    {
14:    public:
15:        CAddressBookDAOSet(CDaoDatabase* pDatabase = NULL);
16:        DECLARE_DYNAMIC(CAddressBookDAOSet)
17:
18:    // Field/Param Data
19:        //{{AFX_FIELD(CAddressBookDAOSet, CDaoRecordset)
20:        long    m_ID;
21:        CString    m_Last_Name;
22:        CString    m_First_Name;
23:        CString    m_Street;
24:        CString    m_City;
25:        CString    m_State;
26:        long    m_Zip;
27:        CString    m_Phone;
28:        //}}AFX_FIELD
29:
30:    // Overrides
31:        // ClassWizard generated virtual function overrides
32:        //{{AFX_VIRTUAL(CAddressBookDAOSet)
33:        public:
34:        virtual CString GetDefaultDBName();  // REVIEW:  Get a comment // here
35:        virtual CString GetDefaultSQL();     // default SQL for 
               // Recordset
36:        virtual void DoFieldExchange(CDaoFieldExchange* pFX); 
               // RFX support
37:        //}}AFX_VIRTUAL
38:
39:    // Implementation
40:    #ifdef _DEBUG
41:        virtual void AssertValid() const;
42:        virtual void Dump(CDumpContext& dc) const;
43:    #endif
44:
45:    };
46:
47:    //{{AFX_INSERT_LOCATION}}
48:    // Microsoft Developer Studio will insert additional declarations 
49:    // immediately before the previous line. 
50:
51:    #endif // 
             !defined(AFX_ADDRESSBOOKDAOSET_H__990F28BF_41F8_11D2_9D79_
           000000000000__INCLUDED_)

On line 34, notice the declaration of GetDefaultDBName(). This is different from the ODBC configuration. Again, it's the file or data source difference between the two APIs. Other than that difference, the declarations appear identical.

In Listing 15.8, you can explore the document class declaration.


Listing 15.8  The DAO CADDRESSBOOKDAODOC Class Declaration

 1:    // AddressBookDAODoc.h : interface of the CAddressBookDAODoc class
 2:    //
 3:    ///////////////////////////////////////////////////////////////////
 4:
 5:    #if !defined(AFX_ADDRESSBOOKDAODOC_H__990F28BB_41F8_11D2_9D79_
           000000000000__INCLUDED_)
 6:    #define AFX_ADDRESSBOOKDAODOC_H__990F28BB_41F8_11D2_9D79_
           000000000000__INCLUDED_
 7:
 8:    #if _MSC_VER >= 1000
 9:    #pragma once
10:    #endif // _MSC_VER >= 1000
11:
12:
13:    class CAddressBookDAODoc : public CDocument
14:    {
15:    protected: // create from serialization only
16:        CAddressBookDAODoc();
17:        DECLARE_DYNCREATE(CAddressBookDAODoc)
18:
19:    // Attributes
20:    public:
21:        CAddressBookDAOSet m_addressBookDAOSet;
22:
23:    // Operations
24:    public:
25:
26:    // Overrides
27:        // ClassWizard generated virtual function overrides
28:        //{{AFX_VIRTUAL(CAddressBookDAODoc)
29:        public:
30:        virtual BOOL OnNewDocument();
31:        virtual void Serialize(CArchive& ar);
32:        //}}AFX_VIRTUAL
33:
34:    // Implementation
35:    public:
36:        virtual ~CAddressBookDAODoc();
37:    #ifdef _DEBUG
38:        virtual void AssertValid() const;
39:        virtual void Dump(CDumpContext& dc) const;
40:    #endif
41:
42:    protected:
43:
44:    // Generated message map functions
45:    protected:
46:        //{{AFX_MSG(CAddressBookDAODoc)
47:        // NOTE - The ClassWizard will add and remove member functions // here.
48:        //    DO NOT EDIT what you see in these blocks of generated 
           //    code !
49:        //}}AFX_MSG
50:        DECLARE_MESSAGE_MAP()
51:    };
52:
53:    ///////////////////////////////////////////////////////////////////
54:
55:    //{{AFX_INSERT_LOCATION}}
56:    // Microsoft Developer Studio will insert additional declarations 
57:    // immediately before the preceding line.
58:
59:    #endif // 
               !defined(AFX_ADDRESSBOOKDAODOC_H__990F28BB_41F8_11D2_9D79_
           000000000000__INCLUDED_)

Notice that this again appears identical to the ODBC MFC Document wrapper class. Notice on line 21 that the document owns the recordset (your coupling).

Finally, the view class is explored in Listing 15.9.


Listing 15.9  The DAO CADDRESSBOOKDAOVIEW Class Declaration

 1:    // AddressBookDAOView.h : interface of the CAddressBookDAOView 
       // class
 2:    //
 3:    ///////////////////////////////////////////////////////////////////////
 4:
 5:    #if !defined(AFX_ADDRESSBOOKDAOVIEW_H__990F28BD_41F8_11D2_9D79_
           000000000000__INCLUDED_)
 6:    #define AFX_ADDRESSBOOKDAOVIEW_H__990F28BD_41F8_11D2_9D79_
           000000000000__INCLUDED_
 7:
 8:    #if _MSC_VER >= 1000
 9:    #pragma once
10:    #endif // _MSC_VER >= 1000
11:
12:    class CAddressBookDAOSet;
13:
14:    class CAddressBookDAOView : public CDaoRecordView
15:    {
16:    protected: // create from serialization only
17:        CAddressBookDAOView();
18:    DECLARE_DYNCREATE(CAddressBookDAOView)
19:
20:    public:
21:        //{{AFX_DATA(CAddressBookDAOView)
22:        enum{ IDD = IDD_ADDRESSBOOKDAO_FORM };
23:        CAddressBookDAOSet* m_pSet;
24:            // NOTE: The ClassWizard will add data members here
25:        //}}AFX_DATA
26:
27:    // Attributes
28:    public:
29:        CAddressBookDAODoc* GetDocument();
30:
31:    // Operations
32:    public:
33:
34:    // Overrides
35:        // ClassWizard generated virtual function overrides
36:        //{{AFX_VIRTUAL(CAddressBookDAOView)
37:        public:
38:        virtual CDaoRecordset* OnGetRecordset();
39:        virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
40:        protected: 
41:        virtual void DoDataExchange(CDataExchange* pDX);  
           // DDX/DDV support
42:        virtual void OnInitialUpdate(); // called first time after 
           // construct
43:        virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
44:        virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
45:        virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
46:        //}}AFX_VIRTUAL
47:
48:    // Implementation
49:    public:
50:        virtual ~CAddressBookDAOView();
51:    #ifdef _DEBUG
52:        virtual void AssertValid() const;
53:        virtual void Dump(CDumpContext& dc) const;
54:    #endif
55:
56:    protected:
57:
58:    // Generated message map functions
59:    protected:
60:    //{{AFX_MSG(CAddressBookDAOView)
61:        // NOTE - The ClassWizard will add and remove member functions // here.
62:         //    DO NOT EDIT what you see in these blocks of generated 
           //    code !
63:    //}}AFX_MSG
64:    DECLARE_MESSAGE_MAP()
65:    };
66:
67:    #ifndef _DEBUG  // debug version in AddressBookDAOView.cpp
68:    inline CAddressBookDAODoc* CAddressBookDAOView::GetDocument()
69:       { return (CAddressBookDAODoc*)m_pDocument; }
70:    #endif
71:
72:    ///////////////////////////////////////////////////////////////////////
73:
74:    AFX_INSERT_LOCATION}}
75:   // Microsoft Developer Studio will insert additional declarations 
76:   // immediately before the preceding line.
77:
78:    #endif // 
              !defined(AFX_ADDRESSBOOKDAOVIEW_H__990F28BD_41F8_11D2_9D79_
           000000000000__INCLUDED_)

Again, this looks identical to the ODBC implementation, and in many respects it is.

Getting Data

If you look implementation>at the OnInitialUpdate routine of your view class, you see that it, too, is identical to the ODBC implementation. Do you see a pattern?

The GetDefaultDBName will return a string indicating the file and path information to the database (see Listing 15.10). In the ODBC implementation, you had to go to the DSN table to find this information.


Listing 15.10  DAO GETDEFAULTDBNAME Implementation

 1:    CString CAddressBookDAOSet::GetDefaultDBName()
 2:    {
 3:        return _T("E:\\Teach_Yourself_stuff\\Database_21\\Day 
                15\\Applications\\AddressBookDB.mdb");
 4:    }

TIP
It's not generally good practice to hard-code string information. Modify this to return a string variable that will contain the file and path declaration.

The DoFieldExchange, shown in Listing 15.11, is similar to the DoDataExchange for a dialog-based view. The data from the database's columns are mapped to the application's memory by this mechanism.


Listing 15.11  The DAO GETDEFAULTSQL and DOFIELDEXCHANGE Implementations

 1: CString CAddressBookDAOSet::GetDefaultSQL()
 2: {
 3:     return _T("[Addresses]");
 4: }
 5: 
 6: void CAddressBookDAOSet::DoFieldExchange(CDaoFieldExchange* pFX)
 7: {
 8:     //{{AFX_FIELD_MAP(CAddressBookDAOSet)
 9:     pFX->SetFieldType(CDaoFieldExchange::outputColumn);
10:     DFX_Long(pFX, _T("[ID]"), m_ID);
11:     DFX_Text(pFX, _T("[Last Name]"), m_Last_Name);
12:     DFX_Text(pFX, _T("[First Name]"), m_First_Name);
13:     DFX_Text(pFX, _T("[Street]"), m_Street);
14:     DFX_Text(pFX, _T("[City]"), m_City);
15:     DFX_Text(pFX, _T("[State]"), m_State);
16:     DFX_Long(pFX, _T("[Zip]"), m_Zip);
17:     DFX_Text(pFX, _T("[Phone]"), m_Phone);
18:     //}}AFX_FIELD_MAP
19: }

Other DAO Classes

Both the ODBC and the DAO Address Book applications, as presented, are quite simplistic, and they really don't do much. What if you decided that you need to add another phone number field to the database? You could use Microsoft Access to add the field and rebuild these applications to read in that field. You would also have to place controls on the views to display your new field. This is a little bit of work, but what if you needed to let the application's users define temporary tables and queries? Except for SQL DDL statements, ODBC doesn't give the programmer a method for modifying the data source's schema. Because DAO is file-based, the programmer is able to easily modify the schema programmatically.

MFC provides two class wrappers that enable the programmer to easily modify the structure of the database. These are CDaoTableDef and CDaoQueryDef. These wrappers map to the TableDef and QueryDef objects directly. These classes contain member functions that enable the programmer to build new database structures as well as modify any database structures deemed as updatable.

In many cases, a database schema might be too limited to provide the application with the exact query information that it needs. Instead of doing multiple queries and saving all this information locally within the application for processing, the CDaoQueryDef function member will enable the programmer to add a query definition to the database and then perform a query on the temporary query just created.

If the database is large, temporary tables can be defined for the application that can hold temporary indexes, report results, running totals, and other such items that might be expensive in the application.

TIP
Although this book isn't intended to instruct the database application developer on programming the older APIs, you would be well adivsed to build a few test applications to solidify your learning. The MFC source code is distributed with the Developer Studio, and it is recommended that the database application programmer review and investigate the wrapper classes.

Summary

After reading this chapter, you will be able to view ODBC and DAO API applications and understand the mechanisms used in ODBC and DAO applications.

MFC AppWizard provides a good start in creating ODBC and DAO appliations. The MFC wrapper classes do much of the work for you as well. The MFC classes automatically perform the data and provide the RFX mechanism. After you define the user interface controls to view the data, your application is off and running. The only code that you have to write is code for reports and other data manipulation.

Q&A

Q
How do I determine which database support I need?
A
On the MFC AppWizard, the database support without file support is primarily for the ODBC API. Remember that ODBC works with data sources, and DAO works with files. If file support is selected, you are essentially using the Jet database engine.
Q
Can I modify the database table by adding fields (columns)?
A
If the application is a DAO application, use the CDaoTableDef class to define a table. The CDaoTableDef has a member function, CanUpdate, that can determine whether field information in the table can be modified. CDaoTableDef can be used to open an existing table or create a new table in a database schema.

Workshop

The Workshop quiz questions test your understanding of today's material. (The answers appear in Appendix F, "Answers.") The exercises encourage you to apply the information you learned today to real-life situations.

Quiz

  1. What is the mechanism that binds the column data in the database with the data in the database?
  2. How is the API attached to the MFC Document/View architecture?
  3. What routine would have to be modified to change the database SQL Query?

Exercises

  1. Taking the ODBC Address Book application, add controls to display the fields to the RecordView. Is the data updated whenever the cursor selectors are pressed to move up and down the list?
  2. Repeat exercise 1 for the DAO application.
  3. What would the code look like for adding another record to the table? Use (Smith, Jennifer, 234 WayWay St., Dublin, OH, 45400, 614-555-0101).

© Copyright, Sams Publishing. All rights reserved.