ADO(ActiveX Data Objects)是基於組件的數據庫編程接口,它是一個和編程語言無關的COM組件系統。
下面內容轉載自:http://www.vckbase.com/document/viewdoc/?id=496 講解挺細的,部分內容有修改。
我在《VC知識庫在線雜誌》第十四期和第十五期上曾發表了兩篇文章——“直接通過ODBC讀、寫Excel表格文件”和“直接通過ADO讀、寫Access文件”,先後給大家介紹了ODBC和ADO兩種數據庫訪問技術的基本使用方法,這次要給大家介紹的是ADO數據庫訪問技術的使用方法。
ADO(Active Data Object,活動數據對象)實際上是一種基於COM(組件對象模型)的自動化接口(IDispatch)技術,並以OLEDB(對象連接和鑲入的數據庫)爲基礎,經過OLEDB精心包裝後的數據庫訪問技術,利用它可以快速的創建數據庫應用程序。 ADO提供了一組非常簡單,將一般通用的數據訪問細節進行封裝的對象。由於ODBC數據源也提供了一般的OLE DB Privider,所以ADO不僅可以應用自身的OLE DB Privider,而且還可以應用所有的ODBC驅動程序。關於OLE DB和ADO的其它詳細情況,讀者可以自行查閱相關書籍或MSDN,這裏就不一一說明了。讓我們直接步入主題:如何掌握ADO這種數據庫訪問技術。ADO的操作方法和前面講過的DAO的操作在很多方面存在相似之處,在這裏,筆者爲了更有效的說明它的使用方法,用VC6.0做了一個示例程序——AdoRWAccess,這個示例程序可以直接通過ADO來操作Access數據庫,示例程序的運行效果如下圖所示:
在示例程序中我們仍採用原庫結構,數據庫名Demo.mdb,庫內表名DemoTable,表內字段名爲Name(姓名)和Age(年齡)的兩個字段,來構造示例程序操作所需的Access數據庫,這也和上兩篇文章的示例源碼中的庫結構相兼容。
下面讓我們看看ADO數據庫訪問技術使用的基本步驟及方法:
首先,要用#import語句來引用支持ADO的組件類型庫(*.tlb),其中類型庫可以作爲可執行程序(DLL、EXE等)的一部分被定位在其自身程序中的附屬資源裏,如:被定位在msado15.dll的附屬資源中,只需要直接用#import引用它既可。可以直接在Stdafx.h文件中加入下面語句來實現:
#import "c:/program files/common files/system/ado/msado15.dll" / no_namespace / rename ("EOF", "adoEOF")
其中路徑名可以根據自己系統安裝的ADO支持文件的路徑來自行設定。當編譯器遇到#import語句時,它會爲引用組件類型庫中的接口生成包裝類,#import語句實際上相當於執行了API涵數LoadTypeLib()。#import語句會在工程可執行程序輸出目錄中產生兩個文件,分別爲*.tlh(類型庫頭文件)及*.tli(類型庫實現文件),它們分別爲每一個接口產生智能指針,併爲各種接口方法、枚舉類型,CLSID等進行聲明,創建一系列包裝方法。語句no_namespace說明ADO對象不使用命名空間,rename
("EOF", "adoEOF")說明將ADO中結束標誌EOF改爲adoEOF,以避免和其它庫中命名相沖突。
其次,在程序初始過程中需要初始化組件,一般可以用CoInitialize(NULL);來實現,這種方法在結束時要關閉初始化的COM,可以用下面語句CoUnInitialize();來實現。在MFC中還可以採用另一種方法來實現初始化COM,這種方法只需要一條語句便可以自動爲我們實現初始化COM和結束時關閉COM的操作,語句如下所示: AfxOleInit();(補充:該語句在CSrvApp::InitInstance()函數中實現)
接着,就可以直接使用ADO的操作了。(補充:在CSrvDlg類中實現)
我們經常使用的只是前面用#import語句引用類型庫時,生成的包裝類.tlh中聲明的智能指針中的三個,它們分別是_ConnectionPtr、_RecordsetPtr和_CommandPtr。下面分別對它們的使用方法進行介紹:
1、_ConnectionPtr智能指針,通常用於打開、關閉一個庫連接或用它的Execute方法來執行一個不返回結果的命令語句(用法和_CommandPtr中的Execute方法類似)。
——打開一個庫連接。先創建一個實例指針,再用Open打開一個庫連接,它將返回一個IUnknown的自動化接口指針。代碼如下所示:
(補充:_ConnectionPtr的open()函數第一個參數中,provider隨着數據庫的不同而有不同的選擇。例子中適用於ACCESS2003版本的數據庫。)
_ConnectionPtr m_pConnection; // 初始化COM,創建ADO連接等操作 AfxOleInit(); m_pConnection.CreateInstance(__uuidof(Connection)); // 在ADO操作中建議語句中要常用try...catch()來捕獲錯誤信息, // 因爲它有時會經常出現一些意想不到的錯誤。jingzhou xu try { // 打開本地Access庫Demo.mdb m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Demo.mdb","","",adModeUnknown); } catch(_com_error e) { AfxMessageBox("數據庫連接失敗,確認數據庫Demo.mdb是否在當前路徑下!"); return FALSE; }
(補充:__uuidof
格式:__uuidof(表達式)
__uuidof關鍵字用來獲得表達式的GUID。這個表達式可以是一種類型名稱、一個指針、引用或者一個類型的數組、一個有這個類型實例化的模板或者這種類型的變量。只要編譯器能使用該參數獲得相關的GUID,那麼該參數就是有效的。
這個參數的特殊情況就是它取0或NULL的時候。在這種情況下,__uuidof會返回一個由0組成的GUID。
可以使用這一關鍵字來提取一個對象uuid擴展屬性的GUID。
注意:必須這樣使用,否則創建智能指針的時候會出錯,下面兩個指針創建的時候也是如此
)——關閉一個庫連接。如果連接狀態有效,則用Close方法關閉它並賦於它空值。代碼如下所示:
if(m_pConnection->State) m_pConnection->Close(); m_pConnection= NULL;
2、_RecordsetPtr智能指針,可以用來打開庫內數據表,並可以對錶內的記錄、字段等進行各種操作。
——打開數據表。打開庫內表名爲DemoTable的數據表,代碼如下:
_RecordsetPtr m_pRecordset; m_pRecordset.CreateInstance(__uuidof(Recordset)); // 在ADO操作中建議語句中要常用try...catch()來捕獲錯誤信息, // 因爲它有時會經常出現一些意想不到的錯誤。jingzhou xu try { m_pRecordset->Open("SELECT * FROM DemoTable", // 查詢DemoTable表中所有字段 theApp.m_pConnection.GetInterfacePtr(), // 獲取庫接庫的IDispatch指針 adOpenDynamic, adLockOptimistic, adCmdText); } catch(_com_error *e) { AfxMessageBox(e->ErrorMessage()); }
——讀取表內數據
使用 BOF 和 EOF 屬性可確定 Recordset 對象是否包含記錄,或者從一個記錄移動到另一個記錄時是否超出 Recordset 對象的限制。
如果當前記錄位於第一個記錄之前,BOF 屬性將返回 True (-1),如果當前記錄爲第一個記錄或位於其後則將返回 False (0)。
如果當前記錄位於 Recordset 對象的最後一個記錄之後 EOF 屬性將返回 True,而當前記錄爲 Recordset 對象的最後一個記錄或位於其前,則將返回 False。
如果 BOF 或 EOF 屬性爲 True,則沒有當前記錄。
如果打開沒有記錄的 Recordset 對象,BOF 和 EOF 屬性將設置爲 True,而 Recordset 對象的 RecordCount 屬性設置爲零。打開至少包含一條記錄Recordset 對象時,第一條記錄爲當前記錄,而 BOF 和 EOF 屬性爲 False。
如果刪除 Recordset 對象中保留的最後記錄,BOF 和 EOF 屬性將保持 False,直到重新安排當前記錄。
以下表格說明不同 BOF 和 EOF 屬性組合所允許的 Move 方法。
MoveFirst, MoveLast |
MovePrevious, Move < 0 |
Move 0 |
MoveNext, Move > 0 |
|
BOF=True, EOF=False |
允許 | 錯誤 | 錯誤 | 允許 |
BOF=False, EOF=True |
允許 | 允許 | 錯誤 | 錯誤 |
同時爲 True | 錯誤 | 錯誤 | 錯誤 | 錯誤 |
同時爲 False | 允許 | 允許 | 允許 | 允許 |
允許使用 Move 方法並不能保證該方法成功定位記錄,只是意味着調用指定的 Move 方法不會產生錯誤。
下表說明當調用各種 Move 方法但未成功定位記錄時 BOF 和 EOF 屬性設置所發生的情況。
BOF | EOF | |
MoveFirst, MoveLast | 設置爲 True | 設置爲 True |
Move 0 | 沒有變化 | 沒有變化 |
MovePrevious, Move < 0 | 設置爲 True | 沒有變化 |
MoveNext, Move > 0 | 沒有變化 |
設置爲 True |
將表內數據全部讀出並顯示在列表框內,m_AccessList爲列表框的成員變量名。如果沒有遇到表結束標誌adoEOF,則用GetCollect(字段名)或m_pRecordset->Fields->GetItem(字段名)->Value方法,來獲取當前記錄指針所指的字段值,然後再用MoveNext()方法移動到下一條記錄位置。代碼如下所示:
_variant_t var; CString strName,strAge; try
//記錄指針不在第一條記錄之前的位置(記錄不爲空)
if(!m_pRecordset->BOF) m_pRecordset->MoveFirst(); else { AfxMessageBox("表內數據爲空"); return; } // 讀入庫中各字段並加入列表框中 while(!m_pRecordset->adoEOF) { var = m_pRecordset->GetCollect("Name");
//記錄集返回值爲結構體_variant_t,用vt成員判斷是否爲空
if(var.vt != VT_NULL) strName = (LPCSTR)_bstr_t(var); var = m_pRecordset->GetCollect("Age"); if(var.vt != VT_NULL) strAge = (LPCSTR)_bstr_t(var); m_AccessList.AddString( strName + " --> "+strAge ); m_pRecordset->MoveNext(); } // 默認列表指向第一項,同時移動記錄指針並顯示 m_AccessList.SetCurSel(0); } catch(_com_error *e) { AfxMessageBox(e->ErrorMessage()); }
——插入記錄。可以先用AddNew()方法新增一個空記錄,再用PutCollect(字段名,值)輸入每個字段的值,最後再Update()更新到庫中數據既可。其中變量m_Name和m_Age分別爲姓名及年齡編輯框的成員變量名。代碼所下所示:
try { // 寫入各字段值 m_pRecordset->AddNew(); m_pRecordset->PutCollect("Name", _variant_t(m_Name)); m_pRecordset->PutCollect("Age", atol(m_Age)); m_pRecordset->Update(); AfxMessageBox("插入成功!"); } catch(_com_error *e) { AfxMessageBox(e->ErrorMessage()); }
——移動記錄指針。移動記錄指針可以通過MoveFirst()方法移動到第一條記錄、MoveLast()方法移動到最後一條記錄、MovePrevious()方法移動到當前記錄的前一條記錄、MoveNext()方法移動到當前記錄的下一條記錄。但我們有時經常需要隨意移動記錄指針到任意記錄位置時,可以使用Move(記錄號)方法來實現,注意: Move()方法是相對於當前記錄來移動指針位置的,正值向後移動、負值向前移動,如:Move(3),當前記錄是3時,它將從記錄3開始往後再移動3條記錄位置。代碼如下所示:
try { int curSel = m_AccessList.GetCurSel(); // 先將指針移向第一條記錄,然後就可以相對第一條記錄來隨意移動記錄指針 m_pRecordset->MoveFirst(); m_pRecordset->Move(long(curSel)); } catch(_com_error *e) { AfxMessageBox(e->ErrorMessage()); }
——修改記錄中字段值。可以將記錄指針移動到要修改記錄的位置處,直接用PutCollect(字段名,值)將新值寫入並Update()更新數據庫既可。可以用上面方法移動記錄指針,修改字段值代碼如下所示:
try { // 假設對第二條記錄進行修改 m_pRecordset->MoveFirst(); m_pRecordset->Move(1); // 從0開始 m_pRecordset->PutCollect("Name", _variant_t(m_Name)); m_pRecordset->PutCollect("Age", atol(m_Age)); m_pRecordset->Update(); } catch(_com_error *e) { AfxMessageBox(e->ErrorMessage()); }
——刪除記錄。刪除記錄和上面修改記錄的操作類似,先將記錄指針移動到要修改記錄的位置,直接用Delete()方法刪除它並用Update()來更新數據庫既可。代碼如下所示:
try { // 假設刪除第二條記錄 m_pRecordset->MoveFirst(); m_pRecordset->Move(1); // 從0開始 m_pRecordset->Delete(adAffectCurrent); // 參數adAffectCurrent爲刪除當前記錄 m_pRecordset->Update(); } catch(_com_error *e) { AfxMessageBox(e->ErrorMessage()); }
——關閉記錄集。直接用Close方法關閉記錄集並賦於其空值。代碼如下所示:
m_pRecordset->Close(); m_pRecordset = NULL;
3、CommandPtr智能指針,可以使用_ConnectionPtr或_RecordsetPtr來執行任務,定義輸出參數,執行存儲過程或SQL語句。
——執行SQL語句。先創建一個_CommandPtr實例指針,再將庫連接和SQL語句做爲參數,執行Execute()方法既可。代碼如下所示:
_CommandPtr m_pCommand; m_pCommand.CreateInstance(__uuidof(Command)); m_pCommand->ActiveConnection = m_pConnection; // 將庫連接賦於它 m_pCommand->CommandText = "SELECT * FROM DemoTable"; // SQL語句 m_pRecordset = m_pCommand->Execute(NULL, NULL,adCmdText); // 執行SQL語句,返回記錄集
——執行存儲過程。執行存儲過程的操作和上面執行SQL語句類似,不同點僅是CommandText參數中不再是SQL語句,而是存儲過程的名字,如Demo。另一個不同點就是在Execute()中參數由adCmdText(執行SQL語句),改爲adCmdStoredProc來執行存儲過程。如果存儲過程中存在輸入、輸出參數的話,需要使用到另一個智能指針_ParameterPtr來逐次設置要輸入、輸出的參數信息,並將其賦於_CommandPtr中Parameters參數來傳遞信息,有興趣的讀者可以自行查找相關書籍或MSDN。執行存儲過程的代碼如下所示:
_CommandPtr m_pCommand; m_pCommand.CreateInstance(__uuidof(Command)); m_pCommand->ActiveConnection = m_pConnection; // 將庫連接賦於它 m_pCommand->CommandText = "Demo"; m_pCommand->Execute(NULL,NULL, adCmdStoredProc);
關於Execute()函數
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options )
其中CommandText是命令字串,通常是SQL命令。
參數RecordsAffected是操作完成後所影響的行數,
參數Options表示CommandText中內容的類型,Options可以取如下值之一:
adCmdText:表明CommandText是文本命令
adCmdTable:表明CommandText是一個表名
adCmdProc:表明CommandText是一個存儲過程
adCmdUnknown:未知
- 頂