天天看點

ADO通路資料庫程式設計筆記

資料庫通路常用技術

DAO(Data Access Object)

  底層是 JET 引擎,主要用來提供對 ACCESS 資料庫的通路,比較新的版本也支援通路其他資料庫,不過對于其他資料庫,需經過 JET 的中間層,通路速度比較差。

在所有對 ACCESS 資料庫的通路方法中, JET是最快的。最新的 JET Engine版本為4.0,對應的 DAO 版本為3.6,可以通路 ACCESS 2000 的資料庫。

RDO(Remote Data Object)

  底層是 ODBC,RDO僅僅是對ODBC API的一個薄包裝層,封裝的比較簡單,是以也具有較快的速度,在ADO出現以前,是通路MS SQL Server最快的方法。有好幾年沒有更新了,原因是MS早已經決定将其淘汰。

ADO(ActiveX Data Object)

  底層是 OLE DB,不僅能通路關系型資料庫,也可以通路非關系型資料庫,是現在最快速的資料庫通路中間層啊!ADO對OLE DB的包裝相當成功,對象模型簡明扼要,沒有一點多餘的東西,功能還遠超DAO、RDO。

ADO程式設計簡介

引用說明

  在相應頭檔案裡,需要加上下面的代碼:

#import "C:/Program Files/Common Files/System/ADO/msado15.dll"

              no_namespace rename("EOF", " adoEOF ")

  這行代碼的作用是,告訴編譯器去哪裡找ADO的庫檔案(可能各個機器上路徑有所不同),然後說明不用namespace,并且将 EOF 更名為 adoEOF,避免常量沖突。

COM庫的初始化

  ADO 以 COM 為基礎,主要部分都是COM元件。初始化COM庫這項工作通常在類的構造函數中完成。

HRESULT hr = S_OK;

hr=::CoInitialize(NULL);  //初始化COM庫

if (!SUCCEEDED(hr))

  //失敗處理語句

主要對象

  Connection用于建立資料庫連接配接,執行不傳回任何結果集的SQL語句。

  Command用于傳回結果集,并提供簡單的方法執行存儲過程或者任何傳回結果集的SQL語句。

  Recordset就是結果集,可進行資料的存取、滾動操作。

建立Connection對象并連接配接資料庫

  _ConnectionPtr是一個Connection的接口,在程式裡建立它的執行個體,通過某個OLE DB provider指向一個資料源,并開啟連接配接。

  首先我們需要添加一個指向Connection對象的指針

_ConnectionPtr m_pConnection;

  下面的代碼示範了如何建立Connection對象執行個體及如何連接配接資料庫并進行異常捕捉。

try

{

  //建立Connection對象

  hr = m_pConnection.CreateInstance("ADODB.Connection");

  if(SUCCEEDED(hr))

  {

    hr = m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=test.mdb",

                                                 "","",adModeUnknown);

  }

}

catch(_com_error e)//捕捉異常

{

   //異常處理程式

}

Connection對象的Open方法

HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID,

                                                   _bstr_t Password, long Options )

  ConnectionString為連接配接字串,UserID是使用者名,Password是登陸密碼,Options是連接配接選項,用于指定Connection對象對資料的更新許可權。Options可以是如下幾個常量:

adModeUnknown:預設。目前的許可權未設定

adModeRead:隻讀

adModeWrite:隻寫

adModeReadWrite:可以讀寫

adModeShareDenyRead:阻止其它Connection對象以讀權限打開連接配接

adModeShareDenyWrite:阻止其它Connection對象以寫權限打開連接配接

adModeShareExclusive:阻止其它Connection對象以讀寫權限打開連接配接

adModeShareDenyNone:阻止其它Connection對象以任何權限打開連接配接

Connection對象中兩個有用的屬性

  ConnectionTimeOut用來設定連接配接的逾時時間,需要在Open之前調用,例如:

m_pConnection->ConnectionTimeout = 5;///設定逾時時間為5秒

m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);

  State屬性指明目前Connection對象的狀态,0表示關閉,1表示已經打開,我們可以通過讀取這個屬性來作相應的處理,例如:

if(m_pConnection->State)

  m_pConnection->Close(); ///如果已經打開了連接配接則關閉它

執行SQL指令并取得結果記錄集

  為了取得結果記錄集,我們定義一個指向Recordset對象的指針: 

_RecordsetPtr m_pRecordset;

  并為其建立Recordset對象的執行個體:  

m_pRecordset.CreateInstance("ADODB.Recordset");

  利用Connection對象的Execute方法執行SQL指令

_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, 

                                                               VARIANT * RecordsAffected, long Options )

  CommandText是指令字串,通常是SQL指令,參數RecorRecordsAffected是操作完成後所影響的行數,參數Options表示CommandText中内容的類型。Options可以取如下值之一:

adCmdText:表明CommandText是文本指令

adCmdTable:表明CommandText是一個表名

adCmdProc:表明CommandText是一個存儲過程

adCmdUnknown:未知

  Execute執行完後傳回一個指向記錄集的指針,下面我們給出具體代碼并作說明。

_variant_t RecordsAffected;

//執行SQL指令:CREATE TABLE建立表格users,users包含四個字段:

//整形ID,字元串username,整形old,日期型birthday

m_pConnection->Execute( "CREATE TABLE users(ID INTEGER,username TEXT,

                                           old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText);

//往表格裡面添加記錄

 m_pConnection->Execute( "INSERT INTO users(ID,username,old,birthday) VALUES

                                           (1, 'Washington',25,'1970/1/1')",&RecordsAffected,adCmdText);

//将所有記錄old字段的值加一

m_pConnection->Execute( "UPDATE users SET old = old+1",

                                           &RecordsAffected,adCmdText);

//執行SQL統計指令得到包含記錄條數的記錄集

m_pRecordset =  m_pConnection->Execute( "SELECT COUNT(*) FROM users",

                                                                      &RecordsAffected,adCmdText);

_variant_t vIndex = (long)0; 

//取得第一個字段的值放入vCount變量

_variant_t vCount = m_pRecordset->GetCollect(vIndex);

m_pRecordset->Close();///關閉記錄集

利用Command對象來執行SQL指令

_CommandPtr m_pCommand;

m_pCommand.CreateInstance("ADODB.Command");

_variant_t vNULL;

vNULL.vt = VT_ERROR;

vNULL.scode = DISP_E_PARAMNOTFOUND;///定義為無參數

//非常關鍵的一句,将建立的連接配接指派給它

m_pCommand->ActiveConnection = m_pConnection;

m_pCommand->CommandText = “SELECT * FROM users”;///指令字串

//執行指令,取得記錄集

m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);

直接用Recordset對象進行查詢取得記錄集

HRESULT Recordset15::Open ( const _variant_t & Source,

                                                 const _variant_t & ActiveConnection, 

                                                 enum CursorTypeEnum CursorType, 

                                                 enum LockTypeEnum LockType,

                                                 long Options )

  Source是資料查詢字元串,ActiveConnection是已經建立好的連接配接(我們需要用Connection對象指針來構造一個_variant_t對象),CursorType是光标類型,LockType是鎖定類型。LockType可以是以下幾個常量:

adLockUnspecified = -1,///未指定

adLockReadOnly = 1,///隻讀記錄集

adLockPessimistic = 2,///悲觀鎖定方式。資料在更新時鎖定其它所有動作

adLockOptimistic = 3,///樂觀鎖定方式。隻有在你調用Update方法時才鎖定記錄。在此之前仍然可以做資料的更新、插入、删除等動作

adLockBatchOptimistic = 4,//樂觀分批更新。編輯時記錄不會鎖定,更改、插入及删除是在批處理模式下完成。

資料集的周遊和更新

(MoveNext,MovePrev,MoveFirst,MoveLast,Update,AddNew,Delete)

  Update方法有下面三種方法調用:給某個Field對象(或某些個)的Value屬性指派,然後調用Update方法;将字段名和字段值作為參數傳給Update方法;将字段名和字段值的數組作為參數傳給Update方法。

  AddNew方法如下調用:直接調用,然後同Update調用方法一;将字段名數組、字段值數組作為參數傳給AddNew方法。

  Delete方法最簡單,直接調用就行了,删除目前記錄!

  根據我們剛才通過執行SQL指令建立好的users表,它包含四個字段:ID,username,old,birthday。以下的代碼實作:打開記錄集,周遊所有記錄,删除第一條記錄,添加三條記錄,移動光标到第二條記錄,更改其年齡,儲存到資料庫。

_variant_t vUsername,vBirthday,vID,vOld;

_RecordsetPtr m_pRecordset;

m_pRecordset.CreateInstance("ADODB.Recordset");

m_pRecordset->Open( "SELECT * FROM users",  _variant_t((IDispatch*)m_pConnection,true),

                                    adOpenStatic,adLockOptimistic,adCmdText);

while(!m_pRecordset->adoEOF)

{

  //取得第1列的值,從0開始計數,你也可以直接給出列的名稱,如下一行

  vID = m_pRecordset->GetCollect(_variant_t((long)0)); 

  vUsername = m_pRecordset->GetCollect("username");///取得username字段的值

  vOld = m_pRecordset->GetCollect("old");

  vBirthday = m_pRecordset->GetCollect("birthday");

  m_pRecordset->MoveNext();///移到下一條記錄

}

m_pRecordset->MoveFirst();///移到首條記錄

m_pRecordset->Delete(adAffectCurrent);///删除目前記錄

//添加三條新記錄并指派

for(int i=0;i<3;i++)

{

  m_pRecordset->AddNew();///添加新記錄

  m_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));

  m_pRecordset->PutCollect("username",_variant_t("葉利欽"));

  m_pRecordset->PutCollect("old",_variant_t((long)71));

  m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));

}

//從第一條記錄往下移動一條記錄,即移動到第二條記錄處

m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));

//修改其年齡

m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));

m_pRecordset->Update();///儲存到庫中

關閉記錄集與連接配接

  記錄集或連接配接都可以用Close方法來關閉

m_pRecordset->Close();///關閉記錄集

m_pConnection->Close();///關閉連接配接

_variant_t 和 _bstr_t

  因為COM必須設計成跨平台,它需要一種更普遍的方式來處理字元串以及其他資料。這就是VARIANT資料類型的來曆,還有BSTR類型。VARIANT就是一個巨大的 union,包含了你能想得到的所有的資料類型。

  _variant_t是一個類,包裝了VARIANT資料類型,并允許我們簡單的對之進行強制類型轉換,_bstr_t對BSTR幹了同樣的事情。

_variant_t Holder;

Holder = MySet->GetCollect("FIELD_1");

m_List.AddString((char*)_bstr_t(Holder));

BLOB資料的儲存

  在實際的開發過程中我們常常需要存儲較大的二進制資料對象,比如:圖像、音頻檔案、或其它二進制資料,這些資料我們稱之為二進制大對象BLOB(Binary Large Object),其存取的方式與普通資料有所差別。

  BLOB類型的資料無法用普通的方式進行存儲,我們需要使用AppendChunk函數。

  AppendChunk包含在Field對象中,原型如下:

HRESULT AppendChunk (const _variant_t & Data );

  從函數原型中可以看到關鍵的問題是我們需把二進制資料指派給VARIANT類型的變量,下面我們給出具體的代碼并作簡單的分析:

  首先我們建立一張名為userinfo的表,包含三個字段:id,username,old,photo,其中photo是一個可以存儲二進制資料的字段。

//假設m_pBMPBuffer指針指向一塊長度為m_nFileLen的二進制資料,

//并且已經成功打開了記錄集對象m_pRecordset

char           *pBuf = m_pBMPBuffer;

VARIANT        varBLOB;

SAFEARRAY      *psa;

SAFEARRAYBOUND rgsabound[1];

m_pRecordset->AddNew();///添加新記錄

m_pRecordset->PutCollect("username",_variant_t("小李"));///填充username字段

m_pRecordset->PutCollect("old",_variant_t((long)28);///填充old字段

if(pBuf)

{

  rgsabound[0].lLbound = 0;

  rgsabound[0].cElements = m_nFileLen;

  psa = SafeArrayCreate(VT_UI1, 1, rgsabound);///建立SAFEARRAY對象

  for (long i = 0; i < (long)m_nFileLen; i++)

    //将pBuf指向的二進制資料儲存到SAFEARRAY對象psa中

    SafeArrayPutElement (psa, &i, pBuf++);

  varBLOB.vt = VT_ARRAY | VT_UI1;///将varBLOB的類型設定為BYTE類型的數組

  varBLOB.parray = psa;///為varBLOB變量指派

  //加入BLOB類型的資料

  m_pRecordset->GetFields()->GetItem("photo")->AppendChunk(varBLOB);

}

m_pRecordset->Update();///儲存我們的資料到庫中

BLOB資料的讀取

  對應于儲存資料時我們所使用的AppendChunk函數,讀取資料應該使用GetChunk函數。GetChunk的原型如下:

_variant_t GetChunk (long Length );

  給出資料的長度後GetChunk将傳回包含資料的VARIANT類型變量,然後我們可以利用SafeArrayAccessData函數得到VARIANT變量中指向資料的char *類型的指針,以友善我們的處理,具體代碼如下:

//得到資料的長度

long lDataSize = m_pRecordset->GetFields()->GetItem("photo")->ActualSize;

if(lDataSize > 0)

{

  _variant_t varBLOB;

  varBLOB = m_pRecordset->GetFields()->GetItem("photo")->GetChunk(lDataSize);

  if(varBLOB.vt == (VT_ARRAY | VT_UI1)) ///判斷資料類型是否正确

  {

    char *pBuf = NULL;  ///得到指向資料的指針

    SafeArrayAccessData(varBLOB.parray,(void **)&pBuf);

    SafeArrayUnaccessData (varBLOB.parray);

  }

}

 通路表:

    MovieSet->Open("Movie ", m_conn.GetInterfacePtr(),

                    adOpenKeyset, adLockOptimistic, adCmdTable);