ADO編程應用

ADO(ActiveX Data Objects)是基於組件的數據庫編程接口,它是一個和編程語言無關的COM組件系統。本文主要介紹用ADO編程所需要注意的技巧和在VC下進行ADO編程的模式,並對C++ Extensions進行了簡單的討論,希望對ADO開發人員有一定的幫助作用。因爲ADO是一個和編程語言無關的COM組件系統,所以這裏討論的要點適用於所有的編程語言和編程環境,比如:VB、VBScript、VC、Java等等。
編程技巧
1.顯式定義對象類型
實際上,這條準則不僅適用於ADO編程,也適用於其他的與COM對象相關的編程。因爲如果一開始就定義變量類型,則編譯器在編譯的時候就可以知道變量的類型,此時編譯器實際上是採用vtable偏移的方式來得到具體的COM對象包含的方法的地址(這一點和C++中虛函數的地址獲取方式類似);但如果一開始不指定變量類型的話,比如簡單地採用如下的語句:
DIM myCon as Object
或者是:
DIM myCon
這樣,編譯器在編譯的時候就不能得到變量的類型,而只能在運行的時候動態地得到方法的信息(通過使用接口IDispatch的Invoke方法來實現),如此爲了得到方法的地址和相關的變量情況就需要在內部進行兩次調用,無疑會降低程序的運行速度。
2.綁定列到具體的字段對象
在程序開始時就建立對字段對象的引用,可以避免在每次得到記錄後,再在Recordset::Fields中進行查找而增加系統的開銷。
例如,可以採用如下所示的代碼:
Private Sub TblBrowse_Click()
Dim fld1 As ADODB.Field
Dim fld2 As ADODB.Field
Dim rs As ADODB.Recordset
set rs=g_cn.execute(...)
'g_cn爲全局對象adodb.connection
Set fld1 = rs.Fields(“id”) '數據表的字段
Set fld2 = rs.Fields(“name”) ’數據表的字段
If rs.BOF = False Then
While rs.BOF = False
Debug.Print fld1.Value
Debug.Print fld2.Value
rs.MoveNext
Wend
End If
rs.Close
End Sub
3.用SQL語句和存儲過程進行數據更新
儘管採用Recordset對象來更新數據是非常方便的,但是它的開銷也大,通過數據源對象返回的查詢集不僅包含了數據,而且也包含了元數據(metadata),在有些時候元數據可能比數據本身還要大,所以最好採用SQL語句來更新數據。還有要使用存儲過程而不是單一的SQL語句來獲取信息。因爲存儲過程是在服務器端執行的,只把結果返回到客戶端,這樣一方面可以降低網絡進行數據交互的開銷,另一方面使系統更加容易維護,並且能保持數據的一致性。
4.使用集合操作單條的SELECT語句
在使用遊標時,最好使用集合的方法對單條的SELECT語句進行操作。Recordset::get_Collect方法和Recordset::put_Collect方法是Recordset 對象的快捷方式,可以快速地得到一個字段的值而不需要獲得關於一個字段的引用。例如,可以採用如下代碼:
Sub Collect()
Dim rs As New Recordset
rs.ActiveConnection = “...”
rs.Source=“一條SQL查詢語句”
rs.Open
Debug.Print rs.Collect(0),rs.Collect(1),rs.Collect(2)
Debug.Print rs!au_id, rs!au_fname, rs!au_lname
End Sub
5.只查詢所需要的數據
儘管很多開發人員都習慣採用“SELECT * FROM TBL”的模式進行查詢,但是爲了提高系統的效率,如果只需要其中某幾個字段的值,最好把這幾個字段直接寫出來,同時需要限定返回記錄集的範圍(通過WHERE子句進行限定)。
6.正確選擇遊標的位置、類型和鎖方式
如果只需要按順序讀取記錄並且不需要滾動和更新記錄,最好使用服務器端遊標(adUseServer)、僅向前遊標(adOpenForwardOnly)和讀加鎖(adLockReadOnly),這樣可以獲得最好的性能。如果需要滾動記錄,採用客戶端遊標(adUseServer)會比採用服務器端遊標所得到的性能要好,因爲ADO系統默認是採用服務器端遊標類型。當然如果數據集合相當大,採用服務器端遊標的性能會好一些。同時需要注意:如果採用客戶端遊標,最好只採用讀加鎖(adLockReadOnly)的鎖類型,因爲如果需要更新數據,客戶端遊標引擎需要得到額外的信息(元數據),而獲取這個信息的代價是非常昂貴的。
7.調整記錄集對象的CacheSize屬性
ADO使用記錄集對象的CacheSize屬性來決定提取和緩存的記錄的數目,當在緩存的範圍內瀏覽數據時,ADO就只從緩存中提取數據。當要瀏覽的數據超出緩存範圍的時候,ADO就釋放當前緩存,提取下一些記錄(提取的數目爲CacheSize所指定的大小),所以必須根據具體的應用程序的情況,來設定CacheSize的大小,保證得到最佳的性能。
8.定義Command對象的參數
在許多數據源中,得到參數信息和執行命令的代價幾乎是一樣的,所以最好自己在程序中定義好Command參數(也就是說要定義好參數的名稱、類型和方向信息),避免一些從數據提供者(Provider)那裏獲取信息的操作。
9.使用原始的OLE DB提供者
MDAC對許多數據源提供了原始的數據提供者,比如SQL Server、Oracle和Access數據庫,這樣就不需要再通過ODBC來獲取數據(也就是說不需要再通過ODBC驅動這一層),這樣的好處是能更快地得到數據,並且能降低磁盤和內存的開銷。
10.斷開Connection連接
如果使用客戶端遊標,就要斷開Connection連接。ADO有一個特徵是當使用客戶端遊標操作Recordset記錄集的時候,不需要和服務器保持聯繫。所以可以充分利用這個特性降低服務器端的開銷(服務器就不需要維護這些連接了)。當操作完記錄集需要更新時,可以重新和數據庫進行連接來更新數據。爲了創建一個可以斷開連接的記錄集,同時需要使用靜態遊標(adOpenStatic)和批處理的加鎖模式(adLockBatchOptimistic)。下面是有關處理的VC代碼:
pRs.CreateInstance(__uuid(Recordset));
pRs->CursorLoction=adUseClient;
pRs->Open(strCmdText,strConnection,adOpenStatic,adLockBatchOptimistic,adCmdText);
pRs->PutRefActiveConnection(NULL);
//對記錄集對象pRs進行操作
//重新和數據庫建立連接
pRs->PutRefActiveConnectio(pCon);
//批量更新數據
pRs->UpdateBatch(adAffectAll);
需要注意的是:當執行批量更新時,必須自己處理數據衝突問題,因爲更新數據時,其他用戶也可能同時正在對該數據進行操作。
11.使用adExecuteNoRecords選項
如果不需要返回記錄,要使用adExecuteNoRecords選項。ADO 2.0包括一個新的執行選項稱爲adExecuteNoRecords。當使用該選項的時候,ADO就不會創建記錄集對象,不設置任何遊標屬性。數據提供者因爲不需要認證集合的屬性而使性能得到優化。具體的例子如下:
con.Execute “insert into tbl values(fv1, fv2) ”, , adExecuteNoRecords
對僅有一條的執行語句採用Connection::Execute方法比使用Recordset::Open方法或者是Command::Execute方法的效果要好,因爲ADO不保留任何命令狀態的信息,因此執行性能就有所改進。
12.使用session/connection緩衝池
因爲數據庫的打開和關閉非常消耗系統資源,因此,使用連接池對基於多層的應用的性能會有很大的提高。當使用MDAC的時候,開發人員本身並不需要考慮對數據庫連接的緩存,MDAC會自動處理它。連接池在兩個層次上提供支持:OLE DB sessions和ODBC連接。如果使用ADO,數據庫連接會自動被OLE DB session緩衝池所緩存;如果使用ODBC,可以利用在ODBC數據源管理中新的連接緩衝池選項對ODBC緩衝進行設置。
實現方法
我們知道,在VB下進行基於ADO的編程相對比較簡單,只要通過reference加載了適當的類型庫後,就可以正常地調用ADO對象。但是對於VC下的基於ADO的數據庫開發就稍微複雜一些。VC中實現對ADO操作通常有三種方法:
●#import方法;
●利用MFC OLE的ClassWizard;
●通過Windows API中COM相關的函數。
在這三種方法中,#import是最方便的方法,它允許產生一個類似VB的類結構,使程序開發變得很方便。下面分別介紹這三種方法。
1.#import方法
在#import方法中,需要提供所要包含的類型庫的路徑和名稱,VC能夠自動產生一個對GUIDs的定義,以及自動生成對ADO對象的封裝。對任何引用的類型庫,VC會在編譯的時候自動生成兩個文件:
●頭文件(.tlh):包含了所列舉的類型和對類型庫中對象的定義;
●實現文件(.tli):對類型庫對象模型中的方法產生封裝。
例如,在stdafx.h文件中增加對msado15.dd的#import之後,VC會產生msado15.tlh和msado15.tli兩個文件。
#import能夠使用一個新的類_com_ptr_t,它也被稱爲智能指針。智能指針能夠自動執行QuyerInterface、AddRef和Release函數。
下面的代碼演示瞭如何使用#import在應用中實現對ADO的操作:
#import “c:/program files/common files/system/ado/msado15.dll” /no_namespace
rename ( “EOF”, “adoEOF” )
重命名EOF是必要的,因爲典型的VC應用都已經定義了EOF作爲常數-1。
通常來說,操作一個自動化對象需要定義和初始化一個用來操作的變量。可以通過使用智能指針
(_com_ptr_t)的構造函數傳遞一個有效的CLSID或者是PROGID,也可以通過_com_ptr_t::CreateInstance()方法來定義對象。具體代碼如下所示:
_ConnectionPtr Conn1( __uuidof( Connection ) );
也可以採用下面的代碼實現同樣的功能:
_ConnectionPtr Conn1 = NULL; //定義對象
HRESULT hr = S_OK;
//創建實例
hr =Conn1.CreateInstance( __uuidof( Connection ) );
推薦採用第二種方式,因爲用第一種方式不能返回一個失敗的HRESULT,所以也就不能判斷ADO連接對象是成功還是失敗,以及失敗的原因。注意這裏的__uuidof( Connection)中的Connection是在.tlh文件中定義的。通過把它傳遞給方法CreateInstance,就可以創建一個有效的ADOConnection對象。
需要注意的是#import的no_namespace屬性,它告訴編譯器該類在不在一個單獨的名字空間中。使用no_namespace意味着不需要在初始化變量時引用名字空間。當然如果在應用中需要導入多個類型庫時,最好不要使用no_namespace,以免引起名字衝突


下面是一個簡單的採用了#import方法的基於ADO應用的示例代碼:
#include <windows.h>
#import <msado15.dll> rename(“EOF”, “adoEOF”)
void main()
{
HRESULT hr = S_OK;
//因爲沒有在#import中指定no_namespace,所以必須採用ADODB::這樣的形式來定義變量類型
ADODB::_RecordsetPtr Rs1 = NULL;
//通過ODBC建立ADO連接
_bstr_t Connect( “DSN=AdoDemo;UID=sa;PWD=;” );
_bstr_t Source ( “SELECT * FROM Authors” );
CoInitialize();
//初始化Rs1對象
hr = Rs1.CreateInstance( __uuidof( ADODB::Recordset ) );
//省略對返回值hr的判斷
Rs1->Open( Source,
Connect,
ADODB::adOpenForwardOnly,
ADODB::adLockReadOnly,
-1 );
//此處可以添加對記錄集Rs1進行操作的代碼
Rs1->Close();
Rs1 = NULL;
::MessageBox( NULL,“Success!”,“”,MB_OK );
CoUninitialize();
}
2.用MFC OLE創建ADO應用
MFC OLE同樣能夠封裝(wrapper)一個類型庫,但是與#import不同,它不能從類型庫中產生枚舉類型。MFC類CString和COleVariant隱藏了BSTRS和Variants的細節。由MFC OLE產生的類都繼承了類ColeDispatchDriver,由ADO產生的失敗的HRESULTS被封裝在類ColeDispatchException中。
用MFC OLE ClassWizard創建ADO應用的步驟如下:
●從Tools菜單中,選擇Options選項中的Directories tab條目,在Show Directories中的Library Files中增加路徑C:/program files/common files/system/ado,設置包含ADO類型庫的路徑。
●從View菜單中,激活ClassWizard,點擊Add Class按鈕並選擇“From A Type Library...”選項,然後在Type Library dialog box對話框中,從C:/program files/common files/system/ado中選擇文件msado15.dll,在Confirm Classes對話框中,選擇所有列出的類並按OK按鈕退出ClassWizard。這樣,ClassWizard便生成了兩個文件msado15.h和msado15.cpp。
下面是實現ADO應用的示例代碼:
//初始化COM對象
AfxOleInit();
...
//定義數據集對象
_Recordset Rs1;
COleException e;
COleVariant Connect( “DSN=AdoDemo;UID=sa;PWD=;” );
COleVariant Source ( “SELECT * FROM Authors” );
//創建數據集對象
Rs1.CreateDispatch(“ADODB.Recordset.2.0”,&e );
Rs1.Open( (VARIANT) Source,
(VARIANT) Connect,
0, 1, -1 );
//此處可以添加對結果集Rs1進行處理的代碼
Rs1.Close();
Rs1.ReleaseDispatch();
AfxMessageBox(“Success!”);
3.用COM API創建ADO工程
#import和MFC OLE都圍繞着一個給定的自動化對象產生了一個封裝類,它們分別繼承自_com_ptr_t和ColeDispatchDriver。其實也可以通過使用Windows API函數直接初始化ADO對象。爲了直接使用ADO和COM對象,需要添加兩個頭文件adoid.h和adoint.h,這兩個頭文件定義了CLSIDs、接口定義和操作ADO類型庫所需要的枚舉類型。此外,還需要增加頭文件INITGUID.H。
爲了能夠編譯用COM API創建的ADO工程文件,還需要在機器中安裝OLE DB SDK或者是MSDASDK工具。下面是利用API創建ADO的簡單的示例代碼:
#include <windows.h>
#include <initguid.h>
#include “adoid.h” // ADO的GUID's
#include “adoint.h” // ADO的類、枚舉等等
void main()
{
HRESULT hr = S_OK;
// ADORecordset 是在adoint.h中定義的
ADORecordset* Rs1 = NULL;
VARIANT Source;
VARIANT Connect;
VariantInit( &Source );
VariantInit( &Connect );
Source.vt = VT_BSTR;
Source.bstrVal = ::SysAllocString( L“SELECT * FROM Authors”);
Connect.vt = VT_BSTR;
Connect.bstrVal = ::SysAllocString( L“DSN=AdoDemo;UID=sa;PWD=;” );
hr = CoCreateInstance( CLSID_CADORecordset,
NULL,
CLSCTX_INPROC_SERVER,
IID_IADORecordset,
(LPVOID *) &Rs1 );
if( SUCCEEDED( hr ) ) hr = Rs1->Open
(Source,
Connect,
adOpenForwardOnly,
adLockReadOnly,
-1 );
//對記錄集Rs1進行處理
if( SUCCEEDED( hr ) ) hr = Rs1->Close();
if( SUCCEEDED( hr ) ) { Rs1->Release(); Rs1 = NULL; }
if( SUCCEEDED( hr ) ) ::MessageBox( NULL, “Success!”, “”, MB_OK );
}
C++ Extensions
如果用C++進行ADO應用程序開發,應該使用ADO C++ Extensions。我們知道,用VB或者VBScript來操作ADO是非常方便的,但是如果使用C++或者是Java,就必須要處理類似Variants這樣的數據結構以實現和C++數據結構的轉換,而這種處理無疑是所有C++開發人員都很頭疼的事情。但如果使用C++ Extensions的話,ADO就不需要從數據提供者處得到列信息,而是在設計時刻使用開發人員提供的列信息。以下是一個簡單的示例:
//創建和具體記錄相對應的類
class CAuthor : public CADORecordBinding
{
BEGIN_ADO_BINDING(CCustomRs1)
ADO_VARIABLE_LENGTH_ENTRY4(1,
adVarChar, m_szau_id, sizeof(m_szau_id), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(2,
adVarChar,m_szau_fname,sizeof(m_szau_fname), FALSE)
ADO_VARIABLE_LENGTH_ENTRY4(3,
adVarChar,m_szau_lname,sizeof(m_szau_lname), FALSE)
END_ADO_BINDING()
protected:
char m_szau_id[12];
char m_szau_fname[21];
char m_szau_lname[41];
};
void FetchAuthorData()
{
CAuthor author;
//記錄集對象
_RecordsetPtr pRs;
IADORecordBinding *piAdoRecordBinding;
//獲取COM對象接口指針
pRs.CreateInstance(__uuidof(Recordset));
//得到需要的記錄集
pRs->Open(“select au_id,au_fname,au_lname from Employees”,“Provider=SQLOLEDB;Data Source=sureshk1;Database=pubs;User Id=sa;Password=;”,
adOpenForwardOnly,
adLockReadOnly,
adCmdText);
//查詢接口IADORecordBinding
pRs->QueryInterface(__uuidof(IADORecordBinding),(LPVOID*)&piAdoRecordBinding);
//綁定對象
piAdoRecordBinding->BindToRecordset(&author);
//得到記錄中的相關內容
while (VARIANT_FALSE == pRs->EOF) {
printf(“%s %s %s”, author.m_szau_id,
author.m_szau_fname, author.m_szau_lname);
pRs->MoveNext();
}
//釋放對象
piAdoRecordBinding->Release();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章