讀寫數據庫的技術很多,現在多用ADO。ADO以COM方式提供,所以它的很多行為遵循COM規范。首先,要引入ADO的COM文件,它的位置一般在"C:/Program Files/Common Files/System/ado/msado15.dll"。
1. 引入ADO
打開預編譯頭文件StdAfx.h,寫上引入聲明:
#import "C:/Program Files/Common Files/System/ado/msado15.dll" no_namespace rename("EOF","adoEOF")
解釋一下上句:no_namespace是指忽略命名空間,rename則是把ADO中的EOF重命名為adoEOF。命成什么名字無所謂,但注意聲明中的名字要和代碼中的名字一致。
2. 初始化
用ADO寫代碼前,要將COM初始化。常用手段是在代碼前后加上CoInitialize(NULL)和CoUninitialize()。也可以用AfxOleInit()來初始化COM庫。
3. 三個核心對象
ADO的3個核心對象是連接對象(_Connection)、命令對象(_Command)和記錄集對象(_RecordSet)。其中連接對象是任何操作必須的。很多操作3個核心對象都可以完成。要實例化它們並使用它們提供的方法,不得不說到它們是一種智能指針(Smart Pointer)。在初始化或釋放等操作時,它們是一個對象,用點操作符,其他大部分操作則使用“->”操作符。
4. 實例化
_ConnectionPtr pConn(__uuidof(Connection));
_RecordsetPtr pRec(__uuidof(Recordset));
_CommandPtr pCmd(__uuidof(Command));
如果上面不加參數,則需加上:
pConn.CreateInstance("ADODB.Connection");
pRec.CreateInstance("ADODB.Recordset");
pCmd.CreateInstance("ADODB.Command");
5. 連接數據庫
連接數據庫一般采取字符串連接。這個字符串的獲取方法用了“不能說的秘密”,即任意新建一個txt文件,重命名為.x.udl。然后雙擊此文件,將出現“數據庫連接屬性”窗口。第一個標簽頁“提供程序”列出了所有數據庫引擎,Access, SQL Server, Oracle等,選擇后點下一步跳至第二個標簽頁“連接”,選擇服務器名稱欄可以填上服務器的IP地址,本機則可不填或填點號;填上數據庫用戶名和密碼后就可以選擇數據庫了。點“測試連接”按鈕,成功。確定。用記事本打開x.udl。將會看到它生成的連接字符串。如下:
"Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Password=123;Initial Catalog=cfdata"
此連接串中,Persist Security Info屬性為True時表示在建立連接后仍然保存密碼,一般取False即可。ID和Password屬性只有在上述數據庫屬性對話框中勾選“允許保存密碼”時才會有。自己可以手工添加。Cfdata是我的數據庫名。
C++中連接代碼如下:
pConn->ConnectionString="Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Password=123;Initial Catalog=cfdata";
6. 示例
有些數據庫操作_Connection一個就能完全搞定。如update語句。因為它不需要返回的結果。如下:
pConn->ConnectionString="Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Password=123;Initial Catalog=cfdata";
pConn->Open("","","",adConnectUnspecified); //打開連接。此處參數均已在上述字符串聲明,故可為空
CString strSQL="update table1 set name=’Richard’ where id=1";
_pConn->Execute(_bstr_t(strSQL),NULL,adCmdText);
這里涉及強制類型轉換。COM中的數據類型和常規(如MFC)類型一般都有對應,但需要轉換。如上面的_bstr_t和CString。至於應該轉換成什么類型,看VC環境中的提示即可(這里推薦大家加裝Visual Assitant,使提示功能更完善)。
也有些操作需要返回記錄集,如select語句。這里就至少需要_Connection和_Recordset兩種核心對象,也可以用_Command執行之。下面展示同一操作用3種對象分別實現的代碼。
(1)連接對象
CString strSQL="select * from table1"; //方法1
pRec=pConn->Execute(_bstr_t(strSQL),NULL,adCmdText);
(2)記錄集對象
CString strSQL="select * from table1"; //方法2
pRec->Open(_variant_t(strSQL),(_variant_t)( (IDispatch*)pConn),adOpenDynamic,adLockOptimistic,adCmdText);
其中第二個參數(_variant_t)( (IDispatch*)pConn)指明活動連接,數據類型轉換比較復雜,_variant_t是參數要求的類型,IDispath*則是_variant_t可強制轉換類型類型。也可用下句:
pConnGetInterfacePtr();
(3) 命令對象
CString strSQL="select * from table1"; //方法3
pCmd->put_ActiveConnection((_variant_t)((IDispatch*)pConn));
pCmd->CommandText=_bstr_t(strSQL);
pRec=pCmd->Execute(NULL,NULL,adCmdText);
7. 數據使用
取得記錄集后,將其中數據取出。用一個ListBox讀取其中name字段數據。代碼如下:
while(!pRec->adoEOF)
{
//_bstr_t類型可以視作COM類型字符串和MFC類型字符串之間的橋梁
CString str=LPSTR(_bstr_t(pRec->GetCollect("name")));
((CListBox*)GetDlgItem(IDC_LIST1))->AddString(str);
pRec->MoveNext();
}
上述代碼中用到了adoEOF,要注意直接拷貝第三方代碼時可能會被重命名為rsEOF等,此時則需作相應更改。另外,while循環中的MoveNext()也不要忘了,否則它就成了死循環了。字段值的獲取及轉換也可用下面方法:
_variant_t var=pRec->GetCollect("name");
var.ChangeType(VT_BSTR);
CString str=var.bstrVal;
8. 關閉與釋放
用完相應對象后,需要關閉並釋放之,代碼如下:
pRec->Close();
pConn->Close();
pRec.Release();
pCmd.Release();
pConn.Release();
9. 錯誤捕獲
數據庫操作難免出現錯誤,連接串錯誤,SQL語句錯誤,或返回NULL你卻硬要向表里塞(此錯誤可以在取出后用var.vt!=VT_NULL判斷),所以我們需要把它們放到try…catch段中。ADO在捕獲到錯誤后會拋出_com_error類型異常,我們可以這樣做:
try
{
pConn->ConnectionString="Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Password=123;Initial Catalog=cfdata";
pConn->Open("","","",adConnectUnspecified);
………… //代碼省略
}
catch(_com_error& e)
{
AfxMessageBox(e.ErrorMessage());
AfxMessageBox(e.Description());
}
這里有個疑惑,在捕獲錯誤后,e.ErrorMessage()和e.Description()中放着不同信息,有時前者說得清晰,有的后者說的清晰,搞不清楚,索性就都加上吧。最后,可以再加個catch(…),畢竟ADO之外的地方也可能發生錯誤。
這里,我還犯過一個錯誤,被它整了N久。我把_com_error& e寫成了_com_error* e后面也對應改成->操作符,而且編譯通過,結果一運行程序就崩潰,而且它不告訴我在哪出錯,因為這時的錯誤是_com_error這時卻對着_com_error*來捕獲當然捕不到。這里&只是一個引用,寫不寫無所謂,*是萬萬不可地。(網上書上很多大師級代碼都是用了*,誤導啊)。
10. 其他接口
ADO中除了3個核心對象外,我們還應了解FieldsPtr、FieldPtr、StreamPtr等接口。例如上述示例中,我們可以用FieldsPtr的GetCount()方法獲取其字段個數,用FieldPtr來接收具體字段等。二進制數據如圖像文件等則會用到StreamPtr。
實例代碼:
#include <iostream> #include "vector" #import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF","adoEOF") using namespace std; int main(int argc, char* argv[]) { CoInitialize(NULL); _ConnectionPtr pConn(__uuidof(Connection)); _RecordsetPtr pRec(__uuidof(Recordset)); ////或者采用以下方式: //_ConnectionPtr pConn=NULL; //_RecordsetPtr pRec=NULL; //_CommandPtr pCmd=NULL; //pConn->CreateInstance("ADODB.Connection"); //pRec->CreateInstance("ADODB.Recordset"); //pCmd->CreateInstance("ADODB.Command"); try { _bstr_t strConnect = "Provider=SQLOLEDB;Persist Security Info=True;\ User Id=sa;Password=123456;Initial Catalog=Test;Data Source=localhost"; //_bstr_t strConnect = "Provider=SQLOLEDB;Server=localhost;DataBase=Test;uid=sa;pwd=123456"; pConn->Open(strConnect,"","",adModeUnknown); } catch(_com_error &e) { cout<<"Initial failed!"<<endl; cout<<e.Description()<<endl; cout<<e.HelpFile()<<endl; return 0; } try { pRec = pConn->Execute("select * from CorrespondField",NULL,adCmdText); if(!pRec->BOF) { pRec->MoveFirst(); } else { cout<<"Data is empty!"<<endl; return 0; } vector<_bstr_t> column_name; for(int i=0;i<pRec->Fields->GetCount();i++) { cout<<pRec->Fields->GetItem(_variant_t((long)i))->Name<<" "; column_name.push_back(pRec->Fields->GetItem(_variant_t((long)i))->Name); cout<<endl; } while(!pRec->adoEOF) { for(vector<_bstr_t>::iterator Itr = column_name.begin();Itr!=column_name.end();Itr++) { if (pRec->GetCollect(*Itr).vt != VT_NULL) { cout<<(_bstr_t)pRec->GetCollect(*Itr)<<" "; } else { cout<<"NULL"<<endl; } } pRec->MoveNext(); cout<<endl; } } catch (_com_error &e) { cout<<e.Description()<<endl; cout<<e.HelpFile()<<endl; return 0; } try { pRec->Close(); pConn->Close(); pRec->Release(); pConn->Release(); } catch(_com_error &e) { cout<<e.Description()<<endl; cout<<e.HelpFile()<<endl; return 0; } CoUninitialize(); return 0; }