用VC++6.0開發(fā)多表聯(lián)接的數(shù)據(jù)庫(kù)應(yīng)用程序
本文介紹了用Visual C++6.0開發(fā)數(shù)據(jù)庫(kù)應(yīng)用程序時(shí),使用MFC ODBC類的編程方法,詳細(xì)說明了在MFC ODBC的派生類中設(shè)置SQL語(yǔ)句參數(shù)的方法,實(shí)現(xiàn)了二個(gè)表的關(guān)聯(lián)。
1.引言 開發(fā)Windows應(yīng)用程序時(shí),在很多情況下可能要和數(shù)據(jù)庫(kù)連接。數(shù)據(jù)庫(kù)類型多種多樣,功能結(jié)構(gòu)也各不相同。從比較簡(jiǎn)單的DBASE、FoxPro等到復(fù)雜的SYBASE、Qracle等大型數(shù)據(jù)庫(kù)系統(tǒng)。VC++6.0都提供了一些接口。程序員可利用這些接口方便地開發(fā)數(shù)據(jù)庫(kù)應(yīng)用程序。MFC ODBC類就是其中的一個(gè),在快速生成簡(jiǎn)單一致的接口應(yīng)用程序方面這些類非常有用。用戶不必了解ODBC API和SQL的具體細(xì)節(jié),利用ODBC類即可完成對(duì)數(shù)據(jù)庫(kù)的大部分操作。然而,VC++ Appwizard 生成的數(shù)據(jù)庫(kù)應(yīng)用程序,只是基于單個(gè)數(shù)據(jù)表的數(shù)據(jù)庫(kù)應(yīng)用程序。而實(shí)際應(yīng)用中,往往要求數(shù)據(jù)庫(kù)應(yīng)用程序能關(guān)聯(lián)二個(gè)或多個(gè)數(shù)據(jù)表。VC++的好多書籍對(duì)此只是簡(jiǎn)單的介紹。本文透徹地解決這一問題。 2.ODBC與MFC 2.1 ODBC ODBC(開放數(shù)據(jù)庫(kù)互連)應(yīng)用程序可通過ODBCAPI訪問不同數(shù)據(jù)源中的數(shù)據(jù),每個(gè)不同的數(shù)據(jù)源類型由一個(gè)ODBC驅(qū)動(dòng)程序支持,這個(gè)驅(qū)動(dòng)程序完成了ODBC API程序的核心,并與具體的數(shù)據(jù)庫(kù)通信。ODBC環(huán)境提供了驅(qū)動(dòng)程序管理器(Driver Manager),管理那些與不同數(shù)據(jù)源連接的驅(qū)動(dòng)程序在ODBC32.DLL中執(zhí)行。應(yīng)用程序只需要與驅(qū)動(dòng)程序管理器連接,驅(qū)動(dòng)程序管理器就會(huì)根據(jù)應(yīng)用程序提供的數(shù)據(jù)源名,選擇正確的驅(qū)動(dòng)程序來訪問數(shù)據(jù)源。 要使用ODBC來開發(fā)數(shù)據(jù)庫(kù)應(yīng)用程序,必須使用在控制面板處的ODBC數(shù)據(jù)源管理器,來建立、配制數(shù)據(jù)源。應(yīng)本例應(yīng)用程序需要,按以下步驟建立所需的數(shù)據(jù)源。 1. 雙擊控制面板處的32位的ODBC程序,選擇對(duì)話框中的User DSN(用戶數(shù)據(jù)源名)選項(xiàng)卡。 2. 單擊Add按鈕,然后選擇一個(gè)數(shù)據(jù)源:Microsoft Visual FoxPro Driver。單擊“完成”,進(jìn)入下一步設(shè)置。 3. 在Data Source Name域內(nèi)輸入數(shù)據(jù)源名:DB-FSB。然后選擇Visual FoxPro數(shù)據(jù)庫(kù)的位置。 4. 單擊OK按鈕,返回到控制面板。 2.2 MFC ODBC MFC的數(shù)據(jù)庫(kù)擴(kuò)展部分封裝了使用ODBC數(shù)據(jù)資源的細(xì)節(jié),提供了VC++與ODBC間一種簡(jiǎn)單的調(diào)用接口。MFC的ODBC類主要包括:用來與一個(gè)數(shù)據(jù)源相連的CDatabase類;用來處理從數(shù)據(jù)庫(kù)返回的一組記錄集的CRecordset類;簡(jiǎn)化從Crecordset對(duì)象中得到數(shù)據(jù)的顯示的CRecordView類。 雖然Cdatabase類允許你對(duì)一個(gè)數(shù)據(jù)庫(kù)執(zhí)行SQL語(yǔ)句,但是CRecordset類提供了應(yīng)用程序與數(shù)據(jù)交互的實(shí)質(zhì)。本例應(yīng)用程序使用CRecordset類來操作數(shù)據(jù)源. CRecordset類的主要目的是讓應(yīng)用程序訪問從數(shù)據(jù)庫(kù)中返回的結(jié)果集。在應(yīng)用程序中要使用CRecordset類,可根據(jù)數(shù)據(jù)源并使用VC++中的ClassWizard來創(chuàng)建Crecordset派生類。通常,一個(gè)CRecordset派生類對(duì)應(yīng)用戶數(shù)據(jù)源中的一個(gè)表。每生成一個(gè)Crecordset派生類,就要選擇一個(gè)數(shù)據(jù)源和一個(gè)數(shù)據(jù)源中的一個(gè)表。若生成一個(gè)Crecordset派生類時(shí),選擇了一個(gè)數(shù)據(jù)源中的多個(gè)表,那么Crecordset派生類中的結(jié)果集是多個(gè)表的卡氏積(迪卡爾積)連接,顯然,在實(shí)際應(yīng)用中沒什么意義。應(yīng)用程序通過派生出的Crecordset類可對(duì)記錄集中的記錄進(jìn)行滾動(dòng)、修改、增加和刪除等操作。 CRecordView類具有幾個(gè)增強(qiáng)功能,允許使用對(duì)話框方式(DoDataExchange()函數(shù))直接從記錄集顯示數(shù)據(jù),使得從記錄集中顯示數(shù)據(jù)更為容易。并提供了記錄移動(dòng)等操作。 3. 多表聯(lián)接的數(shù)據(jù)庫(kù)應(yīng)用程序 3. 1本例程序功能: 通過FSB表的BZM字段及DBK1表的HH字段,將Visual FoxPro 的 FSB表與DBK1表(結(jié)構(gòu)如下)關(guān)聯(lián)起來。程序運(yùn)行出現(xiàn)界面如圖1。用鼠標(biāo)點(diǎn)擊工具條的?、?、(、(則FSB表記錄移動(dòng)而DBK1表的記錄沒移動(dòng)。用鼠標(biāo)點(diǎn)擊“關(guān)聯(lián)”按鈕,則DBK1表的記錄和FSB表記錄同步移動(dòng)(BZM編輯框內(nèi)容與HH編輯框內(nèi)容相同)。如果,要求按照具體的關(guān)鍵字值來查詢二個(gè)表中的相關(guān)記錄,那么,在“定位” 編輯框中輸入具體的關(guān)鍵字值,然后,用鼠標(biāo)點(diǎn)擊“關(guān)聯(lián)”按鈕,就會(huì)見到二個(gè)表在新的記錄集實(shí)現(xiàn)關(guān)聯(lián)。 表1: FSB表結(jié)構(gòu) 字段名 類型 備注 BZM C 索引關(guān)鍵字 DGDL1 N DGZD1 N 其它字段 表2: DBK1表 字段名 類型 備注 HH C 索引關(guān)鍵字 BL N ZZCM C 其它字段 基于MFC ODBC類開發(fā)的數(shù)據(jù)庫(kù)應(yīng)用程序,是通過MFC ODBC類使用SQL語(yǔ)句方式操縱數(shù)據(jù)表的。數(shù)據(jù)庫(kù)中表FSB與表DBK1關(guān)聯(lián)查詢的SQL語(yǔ)句是: SELECT * FROM FSB,DBK1 WHERE FSB.BZM=DBK1.HH 由于創(chuàng)建一個(gè)CRecordset派生類時(shí),一般只選擇一個(gè)數(shù)據(jù)源中的一個(gè)表,因此基于MFC ODBC類開發(fā)的數(shù)據(jù)庫(kù)應(yīng)用程序要實(shí)現(xiàn)二個(gè)表關(guān)聯(lián),就要使用CRecordset類的參數(shù)m _strFilter。它相當(dāng)于SQL語(yǔ)句中的WHERE子句。參數(shù)m _strSort相當(dāng)于SQL語(yǔ)句中的GROUP BY子句。要注意m_strFilter字符串中不要包含“WHERE”關(guān)鍵字。本例在表FSB與表DBK1對(duì)應(yīng)的CRecordset派生類中分別使用了mbzm和mhh二個(gè)m _strFilter參數(shù)。用鼠標(biāo)點(diǎn)擊“關(guān)聯(lián)”按鈕時(shí),程序首先根據(jù)“定位” 編輯框中的內(nèi)容作為mbzm的值,在表FSB檢索結(jié)果集。表DBK1對(duì)應(yīng)的CRecordset派生類根據(jù)表FSB對(duì)應(yīng)的CRecordset派生類的當(dāng)前記錄m_bzm值,作為mhh的值實(shí)行檢索,從而得到與表FSB關(guān)鍵字段BZM對(duì)應(yīng)的表DBK1的記錄。實(shí)現(xiàn)了表FSB與表DBK1的關(guān)聯(lián)。由此可見,二表關(guān)聯(lián)的關(guān)鍵是m _strFilter參數(shù)的設(shè)置。 圖 1 3.2 數(shù)據(jù)庫(kù)應(yīng)用程序創(chuàng)建 3.2. 1 創(chuàng)建單表單文檔的數(shù)據(jù)庫(kù)應(yīng)用程序 根據(jù)前面建立的數(shù)據(jù)源DB-FSB,使用VC++ Appwizard 生成一個(gè)單表單、單文檔的數(shù)據(jù)庫(kù)應(yīng)用程序。選擇數(shù)據(jù)源DB-FSB的數(shù)據(jù)表時(shí)應(yīng)選擇FSB.DBF。應(yīng)用程序名為ZF0001(具體步驟可參考有關(guān)VC++資料)。ZF0001應(yīng)用程序中創(chuàng)建了CZf0001Doc、CZf0001Set、CZf0001View等派生類。 3.2.2 設(shè)置m _strFilter參數(shù) 在上一步生成的CZf0001Set類中,按以下方式,在① ② ③程序中設(shè)置m _strFilter參數(shù)(黑體部分的語(yǔ)句都是為CZf0001Set的參數(shù)mbzm而手動(dòng)增加的)。為節(jié)省篇幅,省略程序清單的部分內(nèi)容。 ①. 在Crecordset派生類的定義中,描述了被連接的數(shù)據(jù)源表的字段,并在VC++ Appwizard 生成的程序注釋“// Field/Param Data ”中提示在此可定義參數(shù)。 CRecordset派生類:CZf0001Set的定義 class CZf0001Set : public Crecordset //Crecordset派生類CZf0001Set { public: CZf0001Set(CDatabase* pDatabase = NULL); DECLARE_DYNAMIC(CZf0001Set) // Field/Param Data //{{AFX_FIELD(CZf0001Set, CRecordset) //被綁定的字段 CString m_bzm; CString m_dgqd1; 、、、、、、 //為節(jié)省篇幅,省略部分字段 CString m_bz; //}}AFX_FIELD CString mbzm; // 參數(shù)mbzm // Overrides // ClassWizard generated virtual function overrides 、、、、、、、 virtual void Dump(CDumpContext& dc) const; #endif }; ②.Crecordset派生類:CZf0001Set的構(gòu)造函數(shù) 其中,對(duì)被綁定字段的相應(yīng)內(nèi)存變量進(jìn)行了初始化。 CZf0001Set::CZf0001Set(CDatabase* pdb) : CRecordset(pdb) { //{{AFX_FIELD_INIT(CZf0001Set) m_bzm = _T(""); m_dgqd1 = _T(""); 、、、、、、 m_bz = _T(""); m_nFields = 16; //數(shù)據(jù)源表的記錄字段個(gè)數(shù) //}}AFX_FIELD_INIT m_nDefaultType = snapshot; m_nParams=1; // CZf0001Set的參數(shù)個(gè)數(shù) mbzm=""; //參數(shù)初始化 } ③.記錄字段交換(RFX) 通過使用RFX,MFC框架可以在數(shù)據(jù)庫(kù)和CRecordset類變量之間交換。交換是通過執(zhí)行DoFieldExchange()函數(shù)而建立的。 void CZf0001Set:oFieldExchange(CFieldExchange* pFX) { //{{AFX_FIELD_MAP(CZf0001Set) pFX->SetFieldType(CFieldExchange:utputColumn); RFX_Text(pFX, _T("[bzm]"), m_bzm); RFX_Text(pFX, _T("[dgqd1]"), m_dgqd1); 、、、、、、; RFX_Text(pFX, _T("[dgdl2]"), m_dgdl2); RFX_Text(pFX, _T("[bz]"), m_bz); //}}AFX_FIELD_MAP pFX->SetFieldType(CFieldExchange::param); //把字段類型設(shè)為CFieldExchange::param RFX_Text(pFX,"mbzm",mbzm); //為參數(shù)設(shè)置RFX 宏,如果有多個(gè)參數(shù),必須按SQL的語(yǔ)句中的位置標(biāo)志符的順序設(shè)置,RFX 宏中的參數(shù)的名字如"mbzm",并非用來與參數(shù)匹配,可以自己定義。 } 3.2.3 增加第二個(gè)表,并設(shè)置第二個(gè)表的參數(shù) 在3。2。1創(chuàng)建的數(shù)據(jù)庫(kù)應(yīng)用程序基礎(chǔ)上,進(jìn)入ClassWizard,點(diǎn)擊Add Class...按鈕并在彈出的菜單中選擇New...,然后在Create New Class對(duì)話框中的Name欄中輸入CZf1001,在Base class欄中選擇CRecordset,按Create按鈕。 在彈出的Database Options對(duì)話框中,在ODBC組合框里選擇DB-FSB數(shù)據(jù)源。然后按OK按鈕。在彈出的Select Database Tables對(duì)話框中選擇DBK1表。按OK確認(rèn)。并在所有存在 #include "CZf0001Set.h" 的文件中,都加入#include "CZf1001.h" 。這樣就創(chuàng)建了與DBK1表對(duì)應(yīng)的Crecordset派生類。 在第一步創(chuàng)建的CZf0001Doc類中,增加一個(gè)CZf1001 對(duì)象的指針變量m_zf1002(即:CZf1001* m_zf1002)。 按3.2.2介紹的CZf0001Set類m _strFilter參數(shù)的設(shè)置方法,在CZf1001類中,設(shè)置參數(shù)mhh。 3. 3 參數(shù)mhh及參數(shù)mbzm在CrecordView的派生類CZf0001View中的使用 3.3.1 參數(shù)在CZf0001View::OnInitialUpdate()函數(shù)使用 在CZf0001View::OnInitialUpdate()函數(shù)的開頭部分,調(diào)用CZf0001View:: GetDocument()從文檔類CZf0001Doc類中,返回二個(gè)CrecordSet類(CZf0001Set、CZf1001)的指針。根據(jù)返回的指針,設(shè)置m _strFilter (相當(dāng)于SQL語(yǔ)句的WHERE子句),并確定二個(gè)參數(shù)的初始值。這里要說明一點(diǎn): m_pSet->m_strFilter="BZM like ?"; m_pSet2->m_strFilter="hh like ?"; 語(yǔ)句中的“?”,在調(diào)用Open或Requery時(shí),“?"將分別自動(dòng)地被CZf0001Set::mbzm和 CZf1001::mhh的值取代。例如,指定mbzm為“31001",則m_pSet->m_strFilter將變成"BZM =31001"。這樣用戶只要指定了mbzm,就可以得到所需要的記錄集。CZf0001View::OnInitialUpdate()的程序清單如下(黑體部分的語(yǔ)句是手工增加的): void CZf0001View::OnInitialUpdate() { m_pSet = &GetDocument()->m_zf0001Set; m_pSet2=&GetDocument()->m_zf1002; if(!m_pSet2->Open()) return; m_pSet->m_strFilter="BZM like ?"; m_pSet->mbzm= "%"; //初始選擇所有記錄 m_pSet->m_strSort=""; m_pSet2->m_strFilter="hh like ?"; m_pSet2->mhh=m_pSet->m_bzm; //將表FSB對(duì)應(yīng)的CRecordset派生類的m_bzm的值,作為參數(shù)mhh的值 m_pSet2->m_strSort=""; //檢索的結(jié)果不進(jìn)行排序 m_pSet->m_pDatabase= m_pSet2->m_pDatabase; //共享CDatabase CRecordView::OnInitialUpdate(); GetParentFrame()->RecalcLayout(); ResizeParentToFit(); } 3.3.2 在對(duì)話框中加入編輯框 在資源視圖Dialog的IDD_ZF0001_FORM表單中,加入用戶需要的編輯框。用ClassWizard在第一個(gè)表FSB中選擇有關(guān)字段與它們相連。但是.使用ClassWizard無法找到第二個(gè)表DBK1字段變量,因此,對(duì)于計(jì)劃與第二個(gè)表DBK1字段相連的編輯框,必須用手工修改CRecordView類的DoDataExchange()(對(duì)話框數(shù)據(jù)交換函數(shù))。 在DoDataExchange()函數(shù) “//}}AFX_DATA_MAP” 后面加入有關(guān)內(nèi)容。見下面程序的黑體部分。如果黑體部分語(yǔ)句加在“//}}AFX_DATA_MAP”的前面,那么,要再次修改IDD_ZF0001_FORM表單時(shí),就無法使用ClassWizard. void CZf0001View:oDataExchange(CDataExchange* pDX) { CRecordView:oDataExchange(pDX); //{{AFX_DATA_MAP(CZf0001View) DDX_Control(pDX, IDC_COMBO1, m_comb); DDX_Control(pDX, IDC_EDIT4, m_SS); DDX_FieldText(pDX, IDC_EDIT2, m_pSet->m_bl1, m_pSet); DDX_FieldText(pDX, IDC_EDIT3, m_pSet->m_dgdl1, m_pSet); DDX_FieldCBString(pDX, IDC_COMBO1, m_pSet->m_bzm, m_pSet); DDX_FieldText(pDX, IDC_EDIT5, m_pSet->m_dgqd1, m_pSet); //}}AFX_DATA_MAP DDX_FieldText(pDX, IDC_EDIT1, m_pSet2->m_bl, m_pSet2); DDX_FieldText(pDX, IDC_EDIT6, m_pSet2->m_hh, m_pSet2); DDX_FieldText(pDX, IDC_EDIT7, m_pSet2->m_zzcm, m_pSet2); } 3.3.3 在對(duì)話框中加入一個(gè)按鈕 為演示二個(gè)表關(guān)聯(lián)的效果,在對(duì)話框中加入一個(gè)“關(guān)聯(lián)”按鈕和一個(gè)輸入?yún)?shù)用的"定位"編輯框。并給此按鈕增加單擊事件代碼如下: void CZf0001View::OnButton1() { // TODO: A |
|