10、邦定數(shù)據(jù)
定義一個(gè)綁定類,將其成員變量綁定到一個(gè)指定的記錄集,以方便于訪問記錄集的字段值。
(1). 從CADORecordBinding派生出一個(gè)類:
class CCustomRs : public CADORecordBinding { BEGIN_ADO_BINDING(CCustomRs) ADO_VARIABLE_LENGTH_ENTRY2(3, adVarChar, m_szau_fname, sizeof(m_szau_fname), lau_fnameStatus, false) ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_szau_lname, sizeof(m_szau_lname), lau_lnameStatus, false) ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_szphone, sizeof(m_szphone), lphoneStatus, true) END_ADO_BINDING()
public: CHAR m_szau_fname[22]; ULONG lau_fnameStatus; CHAR m_szau_lname[42]; ULONG lau_lnameStatus; CHAR m_szphone[14]; ULONG lphoneStatus; }; |
其中將要綁定的字段與變量名用BEGIN_ADO_BINDING宏關(guān)聯(lián)起來。每個(gè)字段對(duì)應(yīng)于兩個(gè)變量,一個(gè)存放字段的值,另一個(gè)存放字段的狀態(tài)。字段用從1開始的序號(hào)表示,如1,2,3等等。
特別要注意的是:如果要綁定的字段是字符串類型,則對(duì)應(yīng)的字符數(shù)組的元素個(gè)數(shù)一定要比字段長(zhǎng)度大2(比如m_szau_fname[22],其綁定的字段au_fname的長(zhǎng)度實(shí)際是20),不這樣綁定就會(huì)失敗。我分析多出的2可能是為了存放字符串結(jié)尾的空字符null和BSTR字符串開頭的一個(gè)字(表示BSTR的長(zhǎng)度)。這個(gè)問題對(duì)于初學(xué)者來說可能是一個(gè)意想不到的問題。
CADORecordBinding類的定義在icrsint.h文件里,內(nèi)容是:
class CADORecordBinding { public: STDMETHOD_(const ADO_BINDING_ENTRY*, GetADOBindingEntries) (VOID) PURE; };
BEGIN_ADO_BINDING宏的定義也在icrsint.h文件里,內(nèi)容是: #define BEGIN_ADO_BINDING(cls) public: \ typedef cls ADORowClass; \ const ADO_BINDING_ENTRY* STDMETHODCALLTYPE GetADOBindingEntries() { \ static const ADO_BINDING_ENTRY rgADOBindingEntries[] = {
ADO_VARIABLE_LENGTH_ENTRY2宏的定義也在icrsint.h文件里: #define ADO_VARIABLE_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Size, Status, Modify)\ {Ordinal, \ DataType, \ 0, \ 0, \ Size, \ offsetof(ADORowClass, Buffer), \ offsetof(ADORowClass, Status), \ 0, \ classoffset(CADORecordBinding, ADORowClass), \ Modify},
#define END_ADO_BINDING宏的定義也在icrsint.h文件里: #define END_ADO_BINDING() {0, adEmpty, 0, 0, 0, 0, 0, 0, 0, FALSE}};\ return rgADOBindingEntries;} |
(2). 綁定
_RecordsetPtr Rs1; IADORecordBinding *picRs=NULL; CCustomRs rs; ...... Rs1->QueryInterface(__uuidof(IADORecordBinding), (LPVOID*)&picRs)); picRs->BindToRecordset(&rs); |
派生出的類必須通過IADORecordBinding接口才能綁定,調(diào)用它的BindToRecordset方法就行了。
(3). rs中的變量即是當(dāng)前記錄字段的值
//Set sort and filter condition: // Step 4: Manipulate the data Rs1->Fields->GetItem("au_lname")->Properties->GetItem("Optimize")->Value = true; Rs1->Sort = "au_lname ASC"; Rs1->Filter = "phone LIKE '415 5*'";
Rs1->MoveFirst(); while (VARIANT_FALSE == Rs1->EndOfFile) { printf("Name: %s\t %s\tPhone: %s\n", (rs.lau_fnameStatus == adFldOK ? rs.m_szau_fname : ""), (rs.lau_lnameStatus == adFldOK ? rs.m_szau_lname : ""), (rs.lphoneStatus == adFldOK ? rs.m_szphone : "")); if (rs.lphoneStatus == adFldOK) strcpy(rs.m_szphone, "777"); TESTHR(picRs->Update(&rs)); // Add change to the batch Rs1->MoveNext(); } Rs1->Filter = (long) adFilterNone; ...... if (picRs) picRs->Release(); Rs1->Close(); pConn->Close(); |
只要字段的狀態(tài)是adFldOK,就可以訪問。如果修改了字段,不要忘了先調(diào)用picRs的Update(注意不是Recordset的Update),然后才關(guān)閉,也不要忘了釋放picRs(即picRs->Release();)。
(4). 此時(shí)還可以用IADORecordBinding接口添加新紀(jì)錄
if(FAILED(picRs->AddNew(&rs))) ...... |
11. 訪問長(zhǎng)數(shù)據(jù)
在Microsoft SQL中的長(zhǎng)數(shù)據(jù)包括text、image等這樣長(zhǎng)類型的數(shù)據(jù),作為二進(jìn)制字節(jié)來對(duì)待。
可以用Field對(duì)象的GetChunk和AppendChunk方法來訪問。每次可以讀出或?qū)懭肴繑?shù)據(jù)的一部分,它會(huì)記住上次訪問的位置。但是如果中間訪問了別的字段后,就又得從頭來了。
請(qǐng)看下面的例子:
//寫入一張照片到數(shù)據(jù)庫(kù): VARIANT varChunk; SAFEARRAY *psa; SAFEARRAYBOUND rgsabound[1];
//VT_ARRAY │ VT_UI1 CFile f("h:\\aaa.jpg",Cfile::modeRead); BYTE bVal[ChunkSize+1]; UINT uIsRead=0; //Create a safe array to store the array of BYTES while(1) { uIsRead=f.Read(bVal,ChunkSize); if(uIsRead==0)break; rgsabound[0].cElements =uIsRead; rgsabound[0].lLbound = 0; psa = SafeArrayCreate(VT_UI1,1,rgsabound); for(long index=0;index<uIsRead;index++) { if(FAILED(SafeArrayPutElement(psa,&index,&bVal[index]))) ::MessageBox(NULL,"啊,又出毛病了。","提示",MB_OK │ MB_ICONWARNING); } varChunk.vt = VT_ARRAY│VT_UI1; varChunk.parray = psa; try{ m_pRecordset->Fields->GetItem("photo")->AppendChunk(varChunk); } catch (_com_error &e) { CString str=(char*)e.Description(); ::MessageBox(NULL,str+"\n又出毛病了。","提示",MB_OK │ MB_ICONWARNING); } ::VariantClear(&varChunk); ::SafeArrayDestroyData( psa); if(uIsRead<ChunkSize)break; }//while(1) f.Close();
//從數(shù)據(jù)庫(kù)讀一張照片: CFile f; f.Open("h:\\bbb.jpg",Cfile::modeWrite│Cfile::modeCreate); long lPhotoSize = m_pRecordset->Fields->Item["photo"]->ActualSize; long lIsRead=0;
_variant_t varChunk; BYTE buf[ChunkSize]; while(lPhotoSize>0) { lIsRead=lPhotoSize>=ChunkSize? ChunkSize:lPhotoSize; varChunk = m_pRecordset->Fields-> Item["photo"]->GetChunk(lIsRead); for(long index=0;index<lIsRead;index++) { ::SafeArrayGetElement(varChunk.parray,&index,buf+index); } f.Write(buf,lIsRead); lPhotoSize-=lIsRead; }//while() f.Close(); |
12. 使用SafeArray問題
學(xué)會(huì)使用SafeArray也是很重要的,因?yàn)樵贏DO編程中經(jīng)常要用。它的主要目的是用于automation中的數(shù)組型參數(shù)的傳遞。因?yàn)樵诰W(wǎng)絡(luò)環(huán)境中,數(shù)組是不能直接傳遞的,而必須將其包裝成SafeArray。實(shí)質(zhì)上SafeArray就是將通常的數(shù)組增加一個(gè)描述符,說明其維數(shù)、長(zhǎng)度、邊界、元素類型等信息。SafeArray也并不單獨(dú)使用,而是將其再包裝到VARIANT類型的變量中,然后才作為參數(shù)傳送出去。在VARIANT的vt成員的值如果包含VT_ARRAY│...,那么它所封裝的就是一個(gè)SafeArray,它的parray成員即是指向SafeArray的指針。SafeArray中元素的類型可以是VARIANT能封裝的任何類型,包括VARIANT類型本身。
使用SafeArray的具體步驟:
方法一:
包裝一個(gè)SafeArray:
(1). 定義變量,如:
VARIANT varChunk; SAFEARRAY *psa; SAFEARRAYBOUND rgsabound[1]; |
(2). 創(chuàng)建SafeArray描述符:
uIsRead=f.Read(bVal,ChunkSize);//read array from a file. if(uIsRead==0)break; rgsabound[0].cElements =uIsRead; rgsabound[0].lLbound = 0; psa = SafeArrayCreate(VT_UI1,1,rgsabound); |
(3). 放置數(shù)據(jù)元素到SafeArray:
for(long index=0;index<uIsRead;index++) { if(FAILED(SafeArrayPutElement(psa,&index,&bVal[index]))) ::MessageBox(NULL,"出毛病了。","提示",MB_OK │ MB_ICONWARNING); } |
一個(gè)一個(gè)地放,挺麻煩的。
(4). 封裝到VARIANT內(nèi):
varChunk.vt = VT_ARRAY│VT_UI1; varChunk.parray = psa; |
這樣就可以將varChunk作為參數(shù)傳送出去了。
讀取SafeArray中的數(shù)據(jù)的步驟:
(1). 用SafeArrayGetElement一個(gè)一個(gè)地讀
BYTE buf[lIsRead]; for(long index=0;index<lIsRead;index++) { ::SafeArrayGetElement(varChunk.parray,&index,buf+index); } |
就讀到緩沖區(qū)buf里了。
方法二:
使用SafeArrayAccessData直接讀寫SafeArray的緩沖區(qū):
(1). 讀緩沖區(qū):
BYTE *buf; SafeArrayAccessData(varChunk.parray, (void **)&buf); f.Write(buf,lIsRead); SafeArrayUnaccessData(varChunk.parray); |
(2). 寫緩沖區(qū):
BYTE *buf; ::SafeArrayAccessData(psa, (void **)&buf); for(long index=0;index<uIsRead;index++) { buf[index]=bVal[index]; } ::SafeArrayUnaccessData(psa);
varChunk.vt = VT_ARRAY│VT_UI1; varChunk.parray = psa; |
這種方法讀寫SafeArray都可以,它直接操縱SafeArray的數(shù)據(jù)緩沖區(qū),比用SafeArrayGetElement和SafeArrayPutElement速度快。特別適合于讀取數(shù)據(jù)。但用完之后不要忘了調(diào)用::SafeArrayUnaccessData(psa),否則會(huì)出錯(cuò)的。
13. 使用書簽( bookmark )
書簽可以唯一標(biāo)識(shí)記錄集中的一個(gè)記錄,用于快速地將當(dāng)前記錄移回到已訪問過的記錄,以及進(jìn)行過濾等等。Provider會(huì)自動(dòng)為記錄集中的每一條記錄產(chǎn)生一個(gè)書簽,我們只需要使用它就行了。我們不能試圖顯示、修改或比較書簽。ADO用記錄集的Bookmark屬性表示當(dāng)前記錄的書簽。
用法步驟:
(1). 建立一個(gè)VARIANT類型的變量
_variant_t VarBookmark;
(2). 將當(dāng)前記錄的書簽值存入該變量
也就是記錄集的Bookmark屬性的當(dāng)前值。
VarBookmark = rst->Bookmark;
(3). 返回到先前的記錄
將保存的書簽值設(shè)置到記錄集的書簽屬性中:
// Check for whether bookmark set for a record if (VarBookmark.vt == VT_EMPTY) printf("No Bookmark set!\n"); else rst->Bookmark = VarBookmark; |
設(shè)置完后,當(dāng)前記錄即會(huì)移動(dòng)到該書簽指向的記錄。
14、設(shè)置過濾條件
Recordset對(duì)象的Filter屬性表示了當(dāng)前的過濾條件。它的值可以是以AND或OR連接起來的條件表達(dá)式(不含WHERE關(guān)鍵字)、由書簽組成的數(shù)組或ADO提供的FilterGroupEnum枚舉值。為Filter屬性設(shè)置新值后Recordset的當(dāng)前記錄指針會(huì)自動(dòng)移動(dòng)到滿足過濾條件的第一個(gè)記錄。例如:
rst->Filter = _bstr_t ("姓名='趙薇' AND 性別=’女’"); |
在使用條件表達(dá)式時(shí)應(yīng)注意下列問題:
(1)、可以用圓括號(hào)組成復(fù)雜的表達(dá)式
例如:
rst->Filter = _bstr_t ("(姓名='趙薇' AND 性別=’女’) OR AGE<25"); |
但是微軟不允許在括號(hào)內(nèi)用OR,然后在括號(hào)外用AND,例如:
rst->Filter = _bstr_t ("(姓名='趙薇' OR 性別=’女’) AND AGE<25"); |
必須修改為:
rst->Filter = _bstr_t ("(姓名='趙薇' AND AGE<25) OR (性別=’女’ AND AGE<25)"); |
(2)、表達(dá)式中的比較運(yùn)算符可以是LIKE
LIKE后被比較的是一個(gè)含有通配符*的字符串,星號(hào)表示若干個(gè)任意的字符。
字符串的首部和尾部可以同時(shí)帶星號(hào)*
rst->Filter = _bstr_t ("姓名 LIKE '*趙*' "); |
也可以只是尾部帶星號(hào):
rst->Filter = _bstr_t ("姓名 LIKE '趙*' "); |
Filter屬性值的類型是Variant,如果過濾條件是由書簽組成的數(shù)組,則需將該數(shù)組轉(zhuǎn)換為SafeArray,然后再封裝到一個(gè)VARIANT或_variant_t型的變量中,再賦給Filter屬性。
15、索引與排序
(1)、建立索引
當(dāng)以某個(gè)字段為關(guān)鍵字用Find方法查找時(shí),為了加快速度可以以該字段為關(guān)鍵字在記錄集內(nèi)部臨時(shí)建立索引。只要將該字段的Optimize屬性設(shè)置為true即可,例如:
pRst->Fields->GetItem("姓名")->Properties-> GetItem("Optimize")->PutValue("True"); pRst->Find("姓名 = '趙薇'",1,adSearchForward); ...... pRst->Fields->GetItem("姓名")->Properties-> GetItem("Optimize")->PutValue("False"); pRst->Close(); |
說明:Optimize屬性是由Provider提供的屬性(在ADO中稱為動(dòng)態(tài)屬性),ADO本身沒有此屬性。
(2)、排序
要排序也很簡(jiǎn)單,只要把要排序的關(guān)鍵字列表設(shè)置到Recordset對(duì)象的Sort屬性里即可,例如:
pRstAuthors->CursorLocation = adUseClient; pRstAuthors->Open("SELECT * FROM mytable", _variant_t((IDispatch *) pConnection), adOpenStatic, adLockReadOnly, adCmdText); ...... pRst->Sort = "姓名 DESC, 年齡 ASC"; |
關(guān)鍵字(即字段名)之間用逗號(hào)隔開,如果要以某關(guān)鍵字降序排序,則應(yīng)在該關(guān)鍵字后加一空格,再加DESC(如上例)。升序時(shí)ASC加不加無所謂。本操作是利用索引進(jìn)行的,并未進(jìn)行物理排序,所以效率較高。
但要注意,在打開記錄集之前必須將記錄集的CursorLocation屬性設(shè)置為adUseClient,如上例所示。Sort屬性值在需要時(shí)隨時(shí)可以修改。
16、事務(wù)處理
ADO中的事務(wù)處理也很簡(jiǎn)單,只需分別在適當(dāng)?shù)奈恢谜{(diào)用Connection對(duì)象的三個(gè)方法即可,這三個(gè)方法是:
(1)、在事務(wù)開始時(shí)調(diào)用
(2)、在事務(wù)結(jié)束并成功時(shí)調(diào)用
(3)、在事務(wù)結(jié)束并失敗時(shí)調(diào)用
在使用事務(wù)處理時(shí),應(yīng)盡量減小事務(wù)的范圍,即減小從事務(wù)開始到結(jié)束(提交或回滾)之間的時(shí)間間隔,以便提高系統(tǒng)效率。需要時(shí)也可在調(diào)用BeginTrans()方法之前,先設(shè)置Connection對(duì)象的IsolationLevel屬性值,詳細(xì)內(nèi)容參見MSDN中有關(guān)ADO的技術(shù)資料。
三、使用ADO編程常見問題解答
以下均是針對(duì)MS SQL 7.0編程時(shí)所遇問題進(jìn)行討論。
1、連接失敗可能原因
Enterprise Managemer內(nèi),打開將服務(wù)器的屬性對(duì)話框,在Security選項(xiàng)卡中,有一個(gè)選項(xiàng)Authentication。
如果該選項(xiàng)是Windows NT only,則你的程序所用的連接字符串就一定要包含Trusted_Connection參數(shù),并且其值必須為yes,如:
"Provider=SQLOLEDB;Server=888;Trusted_Connection=yes" ";Database=master;uid=lad;"; |
如果不按上述操作,程序運(yùn)行時(shí)連接必然失敗。
如果Authentication選項(xiàng)是SQL Server and Windows NT,則你的程序所用的連接字符串可以不包含Trusted_Connection參數(shù),如:
"Provider=SQLOLEDB;Server=888;Database=master;uid=lad;pwd=111;"; |
因?yàn)锳DO給該參數(shù)取的默認(rèn)值就是no,所以可以省略。我認(rèn)為還是取默認(rèn)值比較安全一些。
2、改變當(dāng)前數(shù)據(jù)庫(kù)的方法
使用Tansct-SQL中的USE語(yǔ)句即可。
3、如何判斷一個(gè)數(shù)據(jù)庫(kù)是否存在
(1)、可打開master數(shù)據(jù)庫(kù)中一個(gè)叫做SCHEMATA的視圖,其內(nèi)容列出了該服務(wù)器上所有的數(shù)據(jù)庫(kù)名稱。
(2) 、更簡(jiǎn)便的方法是使用USE語(yǔ)句,成功了就存在;不成功,就不存在。例如:
try{ m_pConnect->Execute ( _bstr_t("USE INSURANCE_2002"),NULL, adCmdText│adExecuteNoRecords ); } catch (_com_error &e) { blSuccess=FALSE; CString str="數(shù)據(jù)庫(kù)INSURANCE_2002不存在!\n"; str+=e.Description(); ::MessageBox(NULL,str,"警告",MB_OK │ MB_ICONWARNING); } |
4、判斷一個(gè)表是否存在
(1)、同樣判斷一個(gè)表是否存在,也可以用是否成功地打開它來判斷,十分方便,例如:
try{ m_pRecordset->Open(_variant_t("mytable"), _variant_t((IDispatch *)m_pConnection,true), adOpenKeyset, adLockOptimistic, adCmdTable); } catch (_com_error &e) { ::MessageBox(NULL,"該表不存在。","提示",MB_OK │ MB_ICONWARNING); } |
(2)、要不然可以采用麻煩一點(diǎn)的辦法,就是在MS-SQL服務(wù)器上的每個(gè)數(shù)據(jù)庫(kù)中都有一個(gè)名為sysobjects的表,查看此表的內(nèi)容即知指定的表是否在該數(shù)據(jù)庫(kù)中。
(3)、同樣,每個(gè)數(shù)據(jù)庫(kù)中都有一個(gè)名為TABLES的視圖(View),查看此視圖的內(nèi)容即知指定的表是否在該數(shù)據(jù)庫(kù)中。
5、類型轉(zhuǎn)換問題
(1)、類型VARIANT_BOOL
類型VARIANT_BOOL等價(jià)于short類型。The VARIANT_BOOL is equivalent to short. see it's definition below:
typdef short VARIANT_BOOL
(2)、_com_ptr_t類的類型轉(zhuǎn)換
_ConnectionPtr可以自動(dòng)轉(zhuǎn)換成IDspatch*類型,這是因?yàn)開ConnectionPtr實(shí)際上是_com_ptr_t類的一個(gè)實(shí)例,而這個(gè)類有此類型轉(zhuǎn)換函數(shù)。
同理,_RecordsetPtr和_CommandPtr也都可以這樣轉(zhuǎn)換。
(3)、_bstr_t和_variant_t類
在ADO編程時(shí),_bstr_t和_variant_t這兩個(gè)類很有用,省去了許多BSTR和VARIANT類型轉(zhuǎn)換的麻煩。
6、打開記錄集時(shí)的問題
在打開記錄集時(shí),在調(diào)用Recordset的Open方法時(shí),其最后一個(gè)參數(shù)里一定不能包含adAsyncExecute,否則將因?yàn)槭钱惒讲僮鳎谧x取數(shù)據(jù)時(shí)無法讀到數(shù)據(jù)。
7、異常處理問題
對(duì)所有調(diào)用ADO的語(yǔ)句一定要用try和catch語(yǔ)句捕捉異常,否則在發(fā)生異常時(shí),程序會(huì)異常退出。
8、使用SafeArray問題
在初學(xué)使用中,我曾遇到一個(gè)傷腦筋的問題,一定要注意:
在定義了SAFEARRAY的指針后,如果打算重復(fù)使用多次,則在中間可以調(diào)用::SafeArrayDestroyData釋放數(shù)據(jù),但決不能調(diào)用::SafeArrayDestroyDescriptor,否則必然出錯(cuò),即使調(diào)用SafeArrayCreate也不行。例如:
SAFEARRAY *psa; ...... //When the data are no longer to be used: ::SafeArrayDestroyData( psa); |
我分析在定義psa指針時(shí),一個(gè)SAFEARRAY的實(shí)例(也就是SAFEARRAY描述符)也同時(shí)被自動(dòng)建立了。但是只要一調(diào)用::SafeArrayDestroyDescriptor,描述符就被銷毀了。
所以我認(rèn)為::SafeArrayDestroyDescriptor可以根本就不調(diào)用,即使調(diào)用也必須在最后調(diào)用。
9、重復(fù)使用命令對(duì)象問題
一個(gè)命令對(duì)象如果要重復(fù)使用多次(尤其是帶參數(shù)的命令),則在第一次執(zhí)行之前,應(yīng)將它的Prepared屬性設(shè)置為TRUE。這樣會(huì)使第一次執(zhí)行減慢,但卻可以使以后的執(zhí)行全部加快。
10、綁定字符串型字段問題
如果要綁定的字段是字符串類型,則對(duì)應(yīng)的字符數(shù)組的元素個(gè)數(shù)一定要比字段長(zhǎng)度大2(比如m_szau_fname[22],其綁定的字段au_fname的長(zhǎng)度實(shí)際是20),不這樣綁定就會(huì)失敗。
11、使用AppendChunk的問題
當(dāng)用AddNew方法剛剛向記錄集內(nèi)添加一個(gè)新記錄之后,不能首先向一個(gè)長(zhǎng)數(shù)據(jù)字段(image類型)寫入數(shù)據(jù),必須先向其他字段寫入過數(shù)據(jù)之后,才能調(diào)用AppendChunk寫該字段,否則出錯(cuò)。也就是說,AppendChunk不能緊接在AddNew之后。另外,寫入其他字段后還必須緊接著調(diào)用AppendChunk,而不能調(diào)用記錄集的Update方法后,才調(diào)用AppendChunk,否則調(diào)用AppendChunk時(shí)也會(huì)出錯(cuò)。換句話說,就是必須AppendChunk在前,Update在后。因而這個(gè)時(shí)候就不能使用帶參數(shù)的AddNew了,因?yàn)閹?shù)的AddNew會(huì)自動(dòng)調(diào)用記錄集的Update,所以AppendChunk就跑到Update的后面了,就只有出錯(cuò)了!因此,這時(shí)應(yīng)該用不帶參數(shù)的AddNew。
我推測(cè)這可能是MS SQL 7.0的問題,在MS SQL 2000中則不存在這些問題,但是AppendChunk仍然不能在Update之后。
四、小結(jié)
一般情況下,Connection和Command的Execute用于執(zhí)行不產(chǎn)生記錄集的命令,而Recordset的Open用于產(chǎn)生一個(gè)記錄集,當(dāng)然也不是絕對(duì)的。特別Command主要是用于執(zhí)行參數(shù)化的命令,可以直接由Command對(duì)象執(zhí)行,也可以將Command對(duì)象傳遞給Recordset的Open。