MFC的數(shù)據(jù)庫操作2009-06-03 22:55:48| 分類: 默認(rèn)分類 | 標(biāo)簽: |字號大中小 訂閱
1、 MFC的ODBC類簡介 MFC的ODBC類對較復(fù)雜的ODBC API進(jìn)行了封裝,提供了簡化的調(diào)用接口,從而大大方便了數(shù)據(jù)庫應(yīng)用程序的開發(fā)。程序員不必了解ODBC API和SQL的具體細(xì)節(jié),利用ODBC類即可完成對數(shù)據(jù)庫的大部分操作。 MFC的ODBC類主要包括:
概括地講,CDatabase針對某個(gè)數(shù)據(jù)庫,它負(fù)責(zé)連接數(shù)據(jù)源;CRecordset針對數(shù)據(jù)源中的記錄集,它負(fù)責(zé)對記錄的操作;CRecordView負(fù)責(zé)界面,而CFieldExchange負(fù)責(zé)CRecordset與數(shù)據(jù)源的數(shù)據(jù)交換。 利用AppWizard和ClassWizard,用戶可以方便地建立數(shù)據(jù)庫應(yīng)用程序,但這并不意味著可以對MFC的ODBC類一無所知.讀者應(yīng)注意閱讀后面幾小節(jié)中的內(nèi)容,為學(xué)習(xí)后面的例子打好基礎(chǔ). 2、 CDatabase類要建立與數(shù)據(jù)源的連接,首先應(yīng)構(gòu)造一個(gè)CDatabase對象,然后再調(diào)用CDatabase的Open成員函數(shù).Open函數(shù)負(fù)責(zé)建立連接,其聲明為
參數(shù)lpszDSN指定了數(shù)據(jù)源名(構(gòu)造數(shù)據(jù)源的方法將在后面介紹),在lpszConnect參數(shù)中也可包括數(shù)據(jù)源名,此時(shí)lpszDSN必需為 NULL,若在函數(shù)中未提供數(shù)據(jù)源名且使lpszDSN為NULL,則會顯示一個(gè)數(shù)據(jù)源對話框,用戶可以在該對話框中選擇一個(gè)數(shù)據(jù)源.參數(shù) bExclusive說明是否獨(dú)占數(shù)據(jù)源,由于目前版本的類庫還不支持獨(dú)占方式,故該參數(shù)的值應(yīng)該是FALSE,這說明數(shù)據(jù)源是被共享的.參數(shù) bReadOnly若為TRUE則對數(shù)據(jù)源的連接是只讀的.參數(shù)lpszConnect指定了一個(gè)連接字符串,連接字符串中可以包括數(shù)據(jù)源名、用戶賬號 (ID)和口令等信息,字符串中的"ODBC"表示要連接到一個(gè)ODBC數(shù)據(jù)源上.參數(shù)bUseCursorLib若為TRUE,則會裝載光標(biāo)庫,否則不 裝載,快照需要光標(biāo)庫,動態(tài)集不需要光標(biāo)庫. 若連接成功,函數(shù)返回TRUE,若返回FALSE,則說明用戶在數(shù)據(jù)源對話框中按了Cancel按鈕。若函數(shù)內(nèi)部出現(xiàn)錯(cuò)誤,則框架會產(chǎn)生一個(gè)異常。 下面是一些調(diào)用Open函數(shù)的例子。 CDatabase m_db; //在文檔類中嵌入一個(gè)CDatabase對象 //連接到一個(gè)名為"Student Registration"的數(shù)據(jù)源 m_db.Open("Student Registration"); //在連接數(shù)據(jù)源的同時(shí)指定了用戶賬號和口令
要從一個(gè)數(shù)據(jù)源中脫離,可調(diào)用函數(shù)Close。在脫離后,可以再次調(diào)用Open函數(shù)來建立一個(gè)新的連接.調(diào)用IsOpen可判斷當(dāng)前是否有一個(gè)連接,調(diào)用GetConnect可返回當(dāng)前的連接字符串。函數(shù)的聲明為
CDatabase的析構(gòu)函數(shù)會調(diào)用Close,所以只要?jiǎng)h除了CDatabase對象就可以與數(shù)據(jù)源脫離。 3、CRecordset類 CRecordset類代表一個(gè)記錄集.該類是MFC的ODBC類中最重要、功能最強(qiáng)大的類。 10.5.1 動態(tài)集、快照、光標(biāo)和光標(biāo)庫 在多任務(wù)操作系統(tǒng)或網(wǎng)絡(luò)環(huán)境中,多個(gè)用戶可以共享同一個(gè)數(shù)據(jù)源。共享數(shù)據(jù)的一個(gè)主要問題是如何協(xié)調(diào)各個(gè)用戶對數(shù)據(jù)源的修改。例如,當(dāng)某一個(gè)應(yīng)用改變了數(shù) 據(jù)源中的記錄時(shí),別的連接至該數(shù)據(jù)源的應(yīng)用應(yīng)該如何處理。對于這個(gè)問題,基于MFC的ODBC應(yīng)用程序可以采取幾種不同的處理辦法,這將由程序采用哪種記 錄集決定。 記錄集主要分為快照(Snapshot) 和動態(tài)集(Dynaset)兩種,CRecordset類對這兩者都支持。這兩種記錄集的不同表現(xiàn)在它們對別的應(yīng)用改變數(shù)據(jù)源記錄采取了不同的處理方法。 快照型記錄集提供了對數(shù)據(jù)的靜態(tài)視.快照是個(gè)很形象的術(shù)語,就好象對數(shù)據(jù)源的某些記錄照了一張照片一樣.當(dāng)別的用戶改變了記錄時(shí)(包括修改、添加和刪 除),快照中的記錄不受影響,也就是說,快照不反映別的用戶對數(shù)據(jù)源記錄的改變.直到調(diào)用了CRecordset::Requery重新查詢后,快照才會 反映變化.對于象產(chǎn)生報(bào)告或執(zhí)行計(jì)算這樣的不希望中途變動的工作,快照是很有用的。需要指出的是,快照的這種靜態(tài)特性是相對于別的用戶而言的,它會正確反 映由本身用戶對記錄的修改和刪除,但對于新添加的記錄直到調(diào)用Requery后才能反映到快照中. 動態(tài)集提供了數(shù)據(jù)的動態(tài)視.當(dāng)別的用戶修改或刪除了記錄集中的記錄時(shí),會在動態(tài)集中反映出來:當(dāng)滾動到修改過的記錄時(shí)對其所作的修改會立即反映到動態(tài)集 中,當(dāng)記錄被刪除時(shí),MFC代碼會跳過記錄集中的刪除部分.對于其它用戶添加的記錄,直到調(diào)用Requery時(shí),才會在動態(tài)集中反映出來。本身應(yīng)用程序?qū)?記錄的修改、添加和刪除會反映在動態(tài)集中。當(dāng)數(shù)據(jù)必須是動態(tài)的時(shí)侯,使用動態(tài)集是最適合的。例如,在一個(gè)火車票聯(lián)網(wǎng)售票系統(tǒng)中,顯然應(yīng)該用動態(tài)集隨時(shí)反映 出共享數(shù)據(jù)的變化。 在記錄集中滾動,需要有一個(gè)標(biāo)志來指明滾動后的位置(當(dāng)前位置)。ODBC驅(qū)動程序會維護(hù)一個(gè)光標(biāo),用來跟蹤記錄集的當(dāng)前記錄,可以把光標(biāo)理解成跟蹤記錄集位置的一種機(jī)制。 光標(biāo)庫(Cursor Library)是處于ODBC驅(qū)動程序管理器和驅(qū)動程序之間的動態(tài)鏈接庫(ODBCCR32.DLL).光標(biāo)庫的主要功能是支持快照以及為底層驅(qū)動程序 提供雙向滾動能力,高層次的驅(qū)動程序不需要光標(biāo)庫,因?yàn)樗鼈兪强蓾L動的.光標(biāo)庫管理快照記錄的緩沖區(qū),該緩沖區(qū)反映本程序?qū)τ涗浀男薷暮蛣h除,但不反映其 它用戶對記錄的改變,由此可見,快照實(shí)際上相當(dāng)于當(dāng)前的光標(biāo)庫緩沖區(qū). 應(yīng)注 意的是,快照是一種靜態(tài)光標(biāo)(Static Cursor).靜態(tài)光標(biāo)直到滾動到某個(gè)記錄才能取得該記錄的數(shù)據(jù).因此,要保證所有的記錄都被快照,可以先滾動到記錄集的末尾,然后再滾動到感興趣的第 一個(gè)記錄上.這樣做的缺點(diǎn)是滾動到末尾需要額外的開銷,會降低性能. 與快照不同,動態(tài)集不用光標(biāo)庫維持的緩沖區(qū)來存放記錄.實(shí)際上,動態(tài)集是不使用光標(biāo)庫的,因?yàn)楣鈽?biāo)庫會屏蔽掉一些支持動態(tài)集的底層驅(qū)動程序功能.動態(tài)集是一種鍵集驅(qū)動光標(biāo)(Keyset-Driven Cursor),當(dāng)打開一個(gè)動態(tài)集時(shí),驅(qū)動程序保存記錄集中每個(gè)記錄的鍵.只要光標(biāo)在動態(tài)集中滾動,驅(qū)動程序就會通過鍵來從數(shù)據(jù)源中檢取當(dāng)前記錄,從而保證選取的記錄與數(shù)據(jù)源同步. 從上面的分析中可以看出,快照和動態(tài)集有一個(gè)共同的特點(diǎn),那就是在建立記錄集后,記錄集中的成員就已經(jīng)確定了.這就是為什么兩種記錄集都不能反映別的用戶添加記錄的原因. 10.5.2 域數(shù)據(jù)成員與數(shù)據(jù)交換 CRecordset類代表一個(gè)記錄集.用戶一般需要用ClassWizard創(chuàng)建一個(gè)CRecordset的派生類.ClassWizard可以為派 生的記錄集類創(chuàng)建一批數(shù)據(jù)成員,這些數(shù)據(jù)成員與記錄的各字段相對應(yīng),被稱為字段數(shù)據(jù)成員或域數(shù)據(jù)成員.例如,對于表10.2所示的將在后面例子中使用的數(shù) 據(jù)庫表,ClassWizard會在派生類中加入6個(gè)域數(shù)據(jù)成員,如清單10.1所示.可以看出域數(shù)據(jù)成員與表中的字段名字類似,且類型匹配.
表10.2 stdreg32.mdb中的Section表
清單10.1 派生類中的域數(shù)據(jù)成員
域數(shù)據(jù)成員用來保存某條記錄的各個(gè)字段,它們是程序與記錄之間的緩沖區(qū).域數(shù)據(jù)成員代表當(dāng)前記錄,當(dāng)在記錄集中滾動到某一記錄時(shí),框架自動地把記錄的各 個(gè)字段拷貝到記錄集對象的域數(shù)據(jù)成員中.當(dāng)用戶要修改當(dāng)前記錄或增加新記錄時(shí),程序先將各字段的新值放入域數(shù)據(jù)成員中,然后調(diào)用相應(yīng)的 CRecordset成員函數(shù)把域數(shù)據(jù)成員設(shè)置到數(shù)據(jù)源中. 不難看出,在記 錄集與數(shù)據(jù)源之間有一個(gè)數(shù)據(jù)交換問題.CRecordset類使用"記錄域交換"(Record Field Exchange,縮寫為RFX)機(jī)制自動地在域數(shù)據(jù)成員和數(shù)據(jù)源之間交換數(shù)據(jù).RFX機(jī)制與對話數(shù)據(jù)交換(DDX)類似.CRecordset的成員函 數(shù)DoFieldExchange負(fù)責(zé)數(shù)據(jù)交換任務(wù),在該函數(shù)中調(diào)用了一系列RFX函數(shù).當(dāng)用戶用ClassWizard加入域數(shù)據(jù)成員 時(shí),ClassWizard會自動在DoFieldExchange中建立RFX.典型DoFieldExchange如清單10.2所示: 清單10.2 典型的DoFieldExchange函數(shù) void CSectionSet::DoFieldExchange(CFieldExchange* pFX) { //{{AFX_FIELD_MAP(CSectionSet) pFX->SetFieldType(CFieldExchange::outputColumn); RFX_Text(pFX, _T("[CourseID]"), m_CourseID); RFX_Text(pFX, _T("[SectionNo]"), m_SectionNo); RFX_Text(pFX, _T("[InstructorID]"), m_InstructorID); RFX_Text(pFX, _T("[RoomNo]"), m_RoomNo); RFX_Text(pFX, _T("[Schedule]"), m_Schedule); RFX_Int(pFX, _T("[Capacity]"), m_Capacity); //}}AFX_FIELD_MAP }
10.5.3 SQL查詢 記錄集的建立實(shí)際上主要是一個(gè)查詢過程,SQL的SELECT語句用來查詢數(shù)據(jù)源.在建立記錄集時(shí),CRecordset會根據(jù)一些參數(shù)構(gòu)造一個(gè) SELECT語句來查詢數(shù)據(jù)源,并用查詢的結(jié)果創(chuàng)建記錄集.明白這一點(diǎn)對理解CRecordset至關(guān)重要.SELECT語句的句法如下: SELECT rfx-field-list FROM table-name [WHERE m_strFilter] [ORDER BY m_strSort] 其中table-name是表名,rfx-field-list是選擇的列(字段).WHERE和ORDER BY是兩個(gè)子句,分別用來過濾和排序。下面是SELECT語句的一些例子:
其中第一個(gè)語句從Section表中選擇CourseID和InstructorID字段.第二個(gè)語句從Section表中選擇CourseID為 MATH202且Capacity等于15的記錄,在該語句中使用了象"AND"或"OR"這樣的邏輯連接符.要注意在SQL語句中引用字符串、日期或時(shí) 間等類型的數(shù)據(jù)時(shí)要用單引號括起來,而數(shù)值型數(shù)據(jù)則不用.第三個(gè)語句從Section表中選擇InstructorID列并且按CourseID的升序排 列,若要降序排列,可使用關(guān)鍵字DESC.
10.5.4 記錄集的建立和關(guān)閉 要建立記錄集,首先要構(gòu)造一個(gè)CRecordset派生類對象,然后調(diào)用Open成員函數(shù)查詢數(shù)據(jù)源中的記錄并建立記錄集.在Open函數(shù)中,可能會調(diào)用GetDefaultConnect和GetDefaultSQL函數(shù).函數(shù)的聲明為
表10.3 記錄集的類型
表10.4 創(chuàng)建記錄集時(shí)的常用選項(xiàng)
上面的例子說明,通過合理地安排SQL語句和表名,Open函數(shù)可以十分靈活地查詢數(shù)據(jù)源中的記錄.用戶可以合并多個(gè)表的字段,也可以只選擇記錄中的某些字段,還可以對記錄進(jìn)行過濾和排序. 上一小節(jié)說過,在建立記錄集時(shí),CRecordset會構(gòu)造一個(gè)SELECT語句來查詢數(shù)據(jù)源.如果在調(diào)用Open時(shí)只提供了表名,那么SELECT語 句還缺少選擇列參數(shù)rfx-field-list(參見10.5.3).框架規(guī)定,如果只提供了表名,則選擇列的信息從DoFieldExchange中 的RFX語句里提?。?,如果在調(diào)用Open時(shí)只提供了"Section"表名,那么將會構(gòu)造如下一個(gè)SELECT語句: SELECT CourseID,SectionNo,InstructorID,RoomNo, Schedule,Capacity FROM Section
建立記錄集后,用戶可以隨時(shí)調(diào)用Requery成員函數(shù)來重新查詢和建立記錄集.Requery有兩個(gè)重要用途:
在調(diào)用Requery之前,可調(diào)用CanRestart來判斷記錄集是否支持Requery操作.要記住Requery只能在成功調(diào)用Open后調(diào)用,所以程序應(yīng)調(diào)用IsOpen來判斷記錄集是否已建立.函數(shù)的聲明為
CRecordset類有兩個(gè)公共數(shù)據(jù)成員m_strFilter和m_strSort用來設(shè)置對記錄的過濾和排序.在調(diào)用Open或Requery 前,如果在這兩個(gè)數(shù)據(jù)成員中指定了過濾或排序,那么Open和Requery將按這兩個(gè)數(shù)據(jù)成員指定的過濾和排序來查詢數(shù)據(jù)源. 成員m_strFilter用于指定過濾器.m_strFilter實(shí)際上包含了SQL的WHERE子句的內(nèi)容,但它不含WHERE關(guān)鍵字.使用m_strFilter的一個(gè)例子為: m_pSet->m_strFilter=“CourseID=‘MATH101’”; //只選擇CourseID為MATH101的記錄 if(m_pSet->Open(CRecordset::snapshot, “Section”)) . . . . . . 成員m_strSort用于指定排序.m_strSort實(shí)際上包含了ORDER BY子句的內(nèi)容,但它不含ORDER BY關(guān)鍵字.m_strSort的一個(gè)例子為 m_pSet->m_strSort=“CourseID DESC”; //按CourseID的降序排列記錄 m_pSet->Open(); . . . . . . 事實(shí)上,Open函數(shù)在構(gòu)造SELECT語句時(shí),會把m_strFilter和m_strSort的內(nèi)容放入SELECT語句的WHERE和ORDER BY子句中.如果在Open的lpszSQL參數(shù)中已包括了WHERE和ORDER BY子句,那么m_strFilter和m_strSort必需為空. 調(diào)用無參數(shù)成員函數(shù)Close可以關(guān)閉記錄集.在調(diào)用了Close函數(shù)后,程序可以再次調(diào)用Open建立新的記錄集.CRecordset的析構(gòu)函數(shù)會調(diào)用Close函數(shù),所以當(dāng)刪除CRecordset對象時(shí)記錄集也隨之關(guān)閉。 10.5.5 滾動記錄 CRecordset提供了幾個(gè)成員函數(shù)用來在記錄集中滾動,如下所示.當(dāng)用這些函數(shù)滾動到一個(gè)新記錄時(shí),框架會自動地把新記錄的內(nèi)容拷貝到域數(shù)據(jù)成員中.
如果在建立記錄集時(shí)選擇了CRecordset::skipDeletedRecords選項(xiàng),那么除了SetAbsolutePosition外,在滾動記錄時(shí)將跳過被刪除的記錄,這一點(diǎn)對象FoxPro這樣的數(shù)據(jù)庫十分重要. 如果記錄集是空的,那么調(diào)用上述函數(shù)將產(chǎn)生異常.另外,必須保證滾動沒有超出記錄集的邊界.調(diào)用IsEOF和IsBOF可以進(jìn)行這方面的檢測.
下面是一個(gè)使用IsEOF的例子: while(!m_pSet->IsEOF( )) m_pSet->MoveNext( ); 調(diào)用GetRecordCound可獲得記錄集中的記錄總數(shù),該函數(shù)的聲明為
10.5.6 修改、添加和刪除記錄 要修改當(dāng)前記錄,應(yīng)該按下列步驟進(jìn)行:
要向記錄集中添加新的記錄,應(yīng)該按下列步驟進(jìn)行:
如果記錄集是快照,那么在添加一個(gè)新的記錄后,需要調(diào)用Requery重新查詢,因?yàn)榭煺諢o法反映添加操作. 要?jiǎng)h除記錄集的當(dāng)前記錄,應(yīng)按下面兩步進(jìn)行:
上面提到的函數(shù)聲明為:
在對記錄集進(jìn)行更改以前,程序也許要調(diào)用下列函數(shù)來判斷記錄集是否是可以更改的,因?yàn)槿绻诓荒芨牡挠涗浖羞M(jìn)行修改、添加或刪除將導(dǎo)致異常的產(chǎn)生.
|
|