1. 程式人生 > >20160229 VC++中使用ADO連線資料庫

20160229 VC++中使用ADO連線資料庫



ADO 是目前在Windows環境中比較流行的客戶端資料庫程式設計技術。ADO是建立在OLE DB底層技術之上的高階程式設計介面,因而它兼具有強大的資料處理功能(處理各種不同型別的資料來源、分散式的資料處理等等)和極其簡單、易用的程式設計介面,因而得到了廣泛的應用。而且按微軟公司的意圖,OLE DBADO將逐步取代 ODBCDAO。現在介紹ADO各種應用的文章和書籍有很多,本文著重站在初學者的角度,簡要探討一下在VC++中使用ADO程式設計時的一些問題。我們希望閱讀本文之前,您對ADO技術的基本原理有一些瞭解。一、在VC++中使用ADO程式設計ADO實際上就是由一組Automation

物件構成的元件,因此可以象使用其它任何Automation物件一樣使用ADOADO中最重要的物件有三個:ConnectionCommandRecordset,它們分別表示連線物件、命令物件和記錄集物件。如果您熟悉使用MFC中的ODBC類(CDatabaseCRecordset)程式設計,那麼學習ADO程式設計就十分容易了。  使用ADO程式設計時可以採用以下三種方法之一:1、使用預處理指令#import

#import "C:\Program Files\Common Files\System\ADO\msado15.dll" \
no_namespace rename("EOF", "EndOfFile")

  但要注意不能放在stdAfx.h檔案的開頭,而應該放在所有include指令的後面。否則在編譯時會出錯。程式在編譯過程中,VC++會讀出msado15.dll中的型別庫資訊,自動產生兩個該型別庫的標頭檔案和實現檔案msado15.tlhmsado15.tli(在您的DebugRelease目錄下)。在這兩個檔案裡定義了ADO的所有物件和方法,以及一些列舉型的常量等。我們的程式只要直接呼叫這些方法就行了,與使用MFC中的COleDispatchDriver類呼叫Automation物件十分類似。2、使用MFC中的CIDispatchDriver  就是通過讀取msado15.dll中的型別庫資訊,建立一個

COleDispatchDriver類的派生類,然後通過它呼叫ADO物件。3、直接用COM提供的API  如使用如下程式碼:

CLSID clsid;
HRESULT hr = ::CLSIDFromProgID(L"ADODB.Connection", &clsid);
if(FAILED(hr))
{...}
::CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (void **)
& pDispatch);
if(FAILED(hr))
{...}
  以上三種方法,第一和第二種類似,可能第一種好用一些,第三種程式設計可能最麻煩。但可能第三種方法也是效率最高的,程式的尺寸也最小,並且對ADO的控制能力也最強。  據微軟資料介紹,第一種方法不支援方法呼叫中的預設引數,當然第二種方法也是這樣,但第三種就不是這樣了。採用第三種方法的水平也最高。當你需要繞過ADO而直接呼叫OLE DB底層的方法時,就一定要使用第三種方法了。ADO程式設計的關鍵,就是熟練地運用ADO提供的各種物件(object)、方法(method)、屬性(property)和容器(collection)。另外,如果是在MS SQLOracle等大型資料庫上程式設計,還要能熟練使用SQL語言。

二、使用#import方法的程式設計步驟  這裡建議您使用#import的方法,因為它易學、易用,程式碼也比較簡潔。1新增#import指令  開啟stdafx.h檔案,將下列內容新增到所有的include指令之後:

#include <icrsint.h> //Include support for VC++ Extensions
#import "C:\Program Files\Common Files\System\ADO\msado15.dll" \
no_namespace rename("EOF", "adoEOF")

  其中icrsint.h檔案包含了VC++擴充套件的一些預處理指令、巨集等的定義,用於COM程式設計時使用。2、定義_ConnectionPtr型變數,並建立資料庫連線  建立了與資料庫伺服器的連線後,才能進行其他有關資料庫的訪問和操作。ADO使用Connection物件來建立與資料庫伺服器的連線,所以它相當於MFC中的CDatabase類。和CDatabase類一樣,呼叫Connection物件的Open方法即可建立與伺服器的連線。  資料型別 _ConnectionPtr實際上就是由類模板_com_ptr_t而得到的一個具體的例項類,其定義可以到msado15.tlhcomdef.hcomip.h這三個檔案中找到。在msado15.tlh中有:

_COM_SMARTPTR_TYPEDEF(_Collection, __uuidof(_Collection));

  經巨集擴充套件後就得到了_ConnectionPtr類。_ConnectionPtr類封裝了Connection物件的Idispatch介面指標,及一些必要的操作。我們就是通過這個指標來操縱Connection物件。類似地,後面用到的_CommandPtr_RecordsetPtr型別也是這樣得到的,它們分別表示命令物件指標和記錄集物件的指標。  (1)、連線到MS SQL Server  注意連線字串的格式,提供正確的連線字串是成功連線到資料庫伺服器的第一步,有關連線字串的詳細資訊參見微軟MSDN Library光碟。  本例連線字串中的server_namedatabase_nameuser_namepassword在程式設計時都應該替換成實際的內容。

_ConnectionPtr pMyConnect=NULL;
HRESULT hr=pMyConnect.CreateInstance(__uuidof(Connection)));
if(FAILED(hr))return;

_bstr_t strConnect="Provider=SQLOLEDB; Server=server_name;"
"Database=database_name; uid=user_name; pwd=password;"; 
//connecting to the database server now:
try{pMyConnect->Open(strConnect,"","",NULL);}
catch (_com_error &e)
{
::MessageBox(NULL,e.Description(),"警告",MB_OK │ MB_ICONWARNING);
}

  注意Connection物件的Open方法中的連線字串引數必須是BSTR_bstr_t型別。另外,本例是直接通過OLE DB Provider建立連線,所以無需建立資料來源。  (2)、通過ODBC Driver連線到Database Server連線字串格式與直接用ODBC程式設計時的差不多:

_bstr_t strConnect="DSN=datasource_name; Database=database_name; uid=user_name; pwd=password;";

  此時與ODBC程式設計一樣,必須先建立資料來源。3、定義_RecordsetPtr型變數,並開啟資料集  定義_RecordsetPtr型變數,然後通過它呼叫Recordset物件的Open方法,即可開啟一個數據集。所以Recordset物件與MFC中的CRecordset類類似,它也有當前記錄、當前記錄指標的概念。如:

_RecordsetPtr m_pRecordset;
if(!FAILED(m_pRecordset.CreateInstance( __uuidof( Recordset )))
{
m_pDoc->m_initialized=FALSE;
return;
}

try{
m_pRecordset->Open(_variant_t("mytable"),
_variant_t((IDispatch *)pMyConnect,true), adOpenKeyset,
adLockOptimistic, adCmdTable);
}
catch (_com_error &e)
{
::MessageBox(NULL,"無法開啟mytable表。","提示",
MB_OK │ MB_ICONWARNING);
}
Recordset物件的Open方法非常重要,它的第一個引數可以是一個SQL語句、一個表的名字或一個命令物件等等;第二個引數就是前面建立的連線物件的指標。此外,用ConnectionCommand物件的Execute方法也能得到記錄集,但是隻讀的。

4、讀取當前記錄的資料  我認為讀取資料的最方便的方法如下:

try{
	m_pRecordset->MoveFirst();
	while (m_pRecordset->adoEOF == VARIANT_FALSE)
	{
		//Retrieve column's value: 
		CString sName = (char*)(_bstr_t)(m_pRecordset->Fields->GetItem
			(_variant_t("name"))->Value);
		short cAge = (short)(m_pRecordset->Fields->GetItem
			(_variant_t("age"))->Value);
		//Do something what you want to do:
		......
			m_pRecordset->MoveNext();
	}
}//try
catch (_com_error &e)
{
	CString str = (char*)e.Description();
	::MessageBox(NULL, str + "\n又出毛病了。", "提示",
		MB_OK │ MB_ICONWARNING);
}

  本例中的nameage都是欄位名,讀取的欄位值分別儲存在sNamecAge變數內。例中的FieldsRecordset物件的容器,GetItem方法返回的是Field物件,而Value則是Field物件的一個屬性(即該欄位的值)。通過此例,應掌握操縱物件屬性的方法。例如,要獲得Field物件的Value屬性的值可以直接用屬性名Value來引用它(如上例),但也可以呼叫Get方法,例如:

CString sName=(char*)(_bstr_t)(m_pRecordset->Fields->GetItem
(_variant_t("name"))->GetValue());

  從此例還可以看到,判斷是否到達記錄集的末尾,使用記錄集的adoEOF屬性,其值若為真即到了結尾,反之則未到。判斷是否到達記錄集開頭,則可用BOF屬性。  另外,讀取資料還有一個方法,就是定義一個繫結的類,然後通過繫結的變數得到欄位值(詳見後面的介紹)。5、修改資料  方法一:

try{
m_pRecordset->MoveFirst(); 
while(m_pRecordset->adoEOF==VARIANT_FALSE) 
{ 
m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->Value=_bstr_t("趙薇");
......
m_pRecordset->Update();

m_pRecordset->MoveNext(); 
}
}//try

  改變了Value屬性的值,即改變了欄位的值。  方法二:

m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->PutValue(_bstr_t("趙薇"));

  方法三:就是用定義繫結類的方法(詳見後面的介紹)。6、新增記錄  新記錄新增成功後,即自動成為當前記錄。AddNew方法有兩種形式,一個含有引數,而另一個則不帶引數。  方法一(不帶引數):

// Add new record into this table:
try{
if(!m_pRecordset->Supports(adAddNew)) return;

m_pRecordset->AddNew(); 
m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->Value=_bstr_t("趙薇");
m_pRecordset->Fields->GetItem
(_variant_t("性別"))->Value=_bstr_t("女");
m_pRecordset->Fields->GetItem
(_variant_t("age"))->Value=_variant_t((short)20);
m_pRecordset->Fields->GetItem
(_variant_t("marry"))->Value=_bstr_t("未婚");
m_pRecordset->Update(); 
}//try
catch (_com_error &e)
{
::MessageBox(NULL, "又出毛病了。","提示",MB_OK │ MB_ICONWARNING);
}

  這種方法弄完了還要呼叫Update()  方法二(帶引數):

_variant_t varName[4],narValue[4];
varName[0] = L"姓名";
varName[1] = L"性別";
varName[2] = L"age";
varName[3] = L"marry";
narValue[0]=_bstr_t("趙薇");
narValue[1]=_bstr_t("女");
narValue[2]=_variant_t((short)20);
narValue[3]=_bstr_t("未婚");

const int nCrit = sizeof varName / sizeof varName[0];
// Create SafeArray Bounds and initialize the array
SAFEARRAYBOUND rgsaName[1],rgsaValue[1];
rgsaName[0].lLbound = 0; 
rgsaName[0].cElements = nCrit;
SAFEARRAY *psaName = SafeArrayCreate( VT_VARIANT, 1, rgsaName );
rgsaValue[0].lLbound = 0;
rgsaValue[0].cElements = nCrit;
SAFEARRAY *psaValue = SafeArrayCreate( VT_VARIANT, 1, rgsaValue );
// Set the values for each element of the array
HRESULT hr1=S_OK.hr2=S_OK;
for( long i = 0 ; i < nCrit && SUCCEEDED( hr1 ) && SUCCEEDED( hr2 );i++) 
{ 
hr1=SafeArrayPutElement(psaName, &i,&varName[i]);
hr2=SafeArrayPutElement(psaValue, &i,&narValue[i]); }

// Initialize and fill the SafeArray
VARIANT vsaName,vsaValue; 
vsaName.vt = VT_VARIANT │ VT_ARRAY;
vsaValue.vt = VT_VARIANT │ VT_ARRAY;
V_ARRAY(&vsaName) = psaName;//&vsaName->parray=psaName;
//see definition in oleauto.h file.
V_ARRAY(&vsaValue) = psaValue;

// Add a new record:
m_pRecordset->AddNew(vsaName,vsaValue);
  這種方法不需要呼叫Update,因為新增後,ADO會自動呼叫它。此方法主要是使用SafeArray挺麻煩。  方法三:就是用定義繫結類的方法(詳見後面的介紹)。

7、刪除記錄  呼叫RecordsetDelete方法就行了,刪除的是當前記錄。要了解Delete的其它用法請查閱參考文獻。

try{
m_pRecordset->MoveFirst(); 
while(m_pRecordset->adoEOF==VARIANT_FALSE) 
{
CString sName=(char*)(_bstr_t)(m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->Value);
if(::MessageBox(NULL,"姓名="+sName+"\n刪除她嗎?",
"提示",MB_YESNO │ MB_ICONWARNING)==IDYES)
{
m_pRecordset->Delete(adAffectCurrent); 
m_pRecordset->Update();
}
m_pRecordset->MoveNext(); 
}
}//try
catch (_com_error &e)
{
::MessageBox(NULL,"又出毛病了。","提示",MB_OK │ MB_ICONWARNING);
}

8、使用帶引數的命令Command物件所代表的就是一個Provider能夠理解的命令,如SQL語句等。使用Command物件的關鍵就是把表示命令的語句設定到CommandText屬性中,然後呼叫Command物件的Execute方法就行了。一般情況下在命令中無需使用引數,但有時使用引數,可以增加其靈活性和效率。(1). 建立連線、命令物件和記錄集物件  本例中表示命令的語句就是一個SQL語句(SELECT語句)。SELECT語句中的問號?就代表引數,如果要多個引數,就多放幾個問號,每個問號代表一個引數。

_ConnectionPtr Conn1;
_CommandPtr Cmd1;
ParametersPtr *Params1 = NULL; // Not an instance of a smart pointer.
_ParameterPtr Param1;
_RecordsetPtr Rs1;

try
{
// Create Connection Object (1.5 Version)
Conn1.CreateInstance( __uuidof( Connection ) );
Conn1->ConnectionString = bstrConnect;
Conn1->Open( bstrEmpty, bstrEmpty, bstrEmpty, -1 );
// Create Command Object
Cmd1.CreateInstance( __uuidof( Command ) );
Cmd1->ActiveConnection = Conn1;
Cmd1->CommandText = _bstr_t("SELECT * FROM mytable WHERE age< ?");
}//try

  要注意命令物件必須與連線物件關聯起來才能起作用,本例中將命令物件的ActiveConnection屬性設定為連線物件的指標,即為此目的:

Cmd1->ActiveConnection = Conn1;

(2). 建立引數物件,並給引數賦值

// Create Parameter Object
Param1 = Cmd1->CreateParameter( _bstr_t(bstrEmpty),
adInteger,
adParamInput,
-1,
_variant_t( (long) 5) );
Param1->Value = _variant_t( (long) 5 );
Cmd1->Parameters->Append( Param1 );

  用命令物件的方法來建立一個引數物件,其中的長度引數(第三個)如果是固定長度的型別,就填-1,如果是字串等可變長度的就填其實際長度。Parameters是命令物件的一個容器,它的Append方法就是把建立的引數物件追加到該容器裡。Append進去的引數按先後順序與SQL語句中的問號從左至右一一對應。(3). 執行命令開啟記錄集

// Open Recordset Object
Rs1 = Cmd1->Execute( &vtEmpty, &vtEmpty2, adCmdText );

  但要注意,用CommandConnection物件的Execute方法得到的Recordset是隻讀的。因為在開啟Recordset之前,我們無法設定它的LockType屬性(其預設值為只讀)。而在開啟之後設定LockType不起作用。  我發現用上述方法得到記錄集Rs1後,不但Rs1中的記錄無法修改,即使直接用SQL語句修改同一表中任何記錄都不行。  要想能修改資料,還是要用Recordset自己的Open方法才行,如:

try{
m_pRecordset->Open((IDispatch *) Cmd1, vtMissing,
adOpenStatic, adLockOptimistic, adCmdUnspecified);
}
catch (_com_error &e)
{
::MessageBox(NULL,"mytable表不存在。","提示",MB_OK │ MB_ICONWARNING);
}

Recordset物件的Open方法真是太好了,其第一個引數可以是SQL語句、表名字、命令物件指標等等。9、響應ADO的通知事件  通知事件就是當某個特定事件發生時,由Provider通知客戶程式,換句話說,就是由Provider呼叫客戶程式中的一個特定的方法(即事件的處理函式)。所以為了響應一個事件,最關鍵的就是要實現事件的處理函式。(1). ConnectionEventsVt介面派生出一個類  為了響應_Connection的通知事件,應該從ConnectionEventsVt介面派生出一個類:

class CConnEvent : public ConnectionEventsVt
{
private:
ULONG m_cRef;
public:
CConnEvent() { m_cRef = 0; };
~CConnEvent() {};

STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
STDMETHODIMP raw_InfoMessage( 
struct Error *pError,
EventStatusEnum *adStatus,
struct _Connection *pConnection);
STDMETHODIMP raw_BeginTransComplete( 
LONG TransactionLevel,
struct Error *pError,
EventStatusEnum *adStatus,
struct _Connection *pConnection);
......
};

(2). 實現每一個事件的處理函式(凡是帶raw_字首的方法都把它實現了)

STDMETHODIMP CConnEvent::raw_InfoMessage( 
struct Error *pError,
EventStatusEnum *adStatus,
struct _Connection *pConnection)
{
*adStatus = adStatusUnwantedEvent;
return S_OK;
};

  有些方法雖然你並不需要,但也必須實現它,只需簡單地返回一個S_OK即可。但如果要避免經常被呼叫,還應在其中將adStatus引數設定為adStatusUnwantedEvent,則在本次呼叫後,以後就不會被呼叫了。另外還必須實現QueryInterface, AddRef,Release三個方法

STDMETHODIMP CConnEvent::QueryInterface(REFIID riid, void ** ppv) 
{
*ppv = NULL;
if (riid == __uuidof(IUnknown) ││ 
riid == __uuidof(ConnectionEventsVt)) *ppv = this;
if (*ppv == NULL)
return ResultFromScode(E_NOINTERFACE);
AddRef();
return NOERROR;
}
STDMETHODIMP_(ULONG) CConnEvent::AddRef() { return ++m_cRef; };
STDMETHODIMP_(ULONG) CConnEvent::Release()
{ 
if (0 != --m_cRef) return m_cRef;
delete this;
return 0;
}

(3). 開始響應通知事件

// Start using the Connection events
IConnectionPointContainer *pCPC = NULL;
IConnectionPoint *pCP = NULL;

hr = pConn.CreateInstance(__uuidof(Connection));
if (FAILED(hr)) return;

hr = pConn->QueryInterface(__uuidof(IConnectionPointContainer), 
(void **)&pCPC);
if (FAILED(hr)) return;
hr = pCPC->FindConnectionPoint(__uuidof(ConnectionEvents), &pCP);
pCPC->Release();
if (FAILED(hr)) return;

pConnEvent = new CConnEvent();
hr = pConnEvent->QueryInterface(__uuidof(IUnknown), (void **) &pUnk);
if (FAILED(hr)) return rc; 
hr = pCP->Advise(pUnk, &dwConnEvt);
pCP->Release();
if (FAILED(hr)) return;

pConn->Open("dsn=Pubs;", "sa", "", adConnectUnspecified);

  也就是說在連線(Open)之前就做這些事。(4). 停止響應通知事件

pConn->Close();
// Stop using the Connection events
hr = pConn->QueryInterface(__uuidof(IConnectionPointContainer), 
(void **) &pCPC);
if (FAILED(hr)) return;
hr = pCPC->FindConnectionPoint(__uuidof(ConnectionEvents), &pCP);
pCPC->Release();
if (FAILED(hr)) return rc;
hr = pCP->Unadvise( dwConnEvt );
pCP->Release();
if (FAILED(hr)) return;
  在連線關閉之後做這件事。

10、邦定資料  定義一個繫結類,將其成員變數繫結到一個指定的記錄集,以方便於訪問記錄集的欄位值。(1). CADORecordBinding派生出一個類:

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巨集關聯起來。每個欄位對應於兩個變數,一個存放欄位的值,另一個存放欄位的狀態。欄位用從1開始的序號表示,如123等等。  特別要注意的是:如果要繫結的欄位是字串型別,則對應的字元陣列的元素個數一定要比欄位長度大2(比如m_szau_fname[22],其繫結的欄位au_fname的長度實際是20),不這樣繫結就會失敗。我分析多出的2可能是為了存放字串結尾的空字元nullBSTR字串開頭的一個字(表示BSTR的長度)。這個問題對於初學者來說可能是一個意想不到的問題。CADORecordBinding類的定義在icrsint.h檔案裡,內容是:

class CADORecordBinding
{
public:
STDMETHOD_(const ADO_BINDING_ENTRY*, GetADOBindingEntries) (VOID) PURE;
};

BEGIN_ADO_BINDING巨集的定義也在icrsint.h檔案裡,內容是:
#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接口才能繫結,呼叫它的BindToRecordset方法就行了。(3). rs中的變數即是當前記錄欄位的值

//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();

  只要欄位的狀態是adFldOK,就可以訪問。如果修改了欄位,不要忘了先呼叫picRsUpdate(注意不是RecordsetUpdate),然後才關閉,也不要忘了釋放picRs(即picRs->Release();)。(4). 此時還可以用IADORecordBinding介面新增新紀錄

if(FAILED(picRs->AddNew(&rs)))
......

11. 訪問長資料  在Microsoft SQL中的長資料包括textimage等這樣長型別的資料,作為二進位制位元組來對待。  可以用Field物件的GetChunkAppendChunk方法來訪問。每次可以讀出或寫入全部資料的一部分,它會記住上次訪問的位置。但是如果中間訪問了別的欄位後,就又得從頭來了。  請看下面的例子:

//寫入一張照片到資料庫:
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();

//從資料庫讀一張照片:
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問題  學會使用SafeArray也是很重要的,因為在ADO程式設計中經常要用。它的主要目的是用於automation中的陣列型引數的傳遞。因為在網路環境中,陣列是不能直接傳遞的,而必須將其包裝成SafeArray。實質上SafeArray就是將通常的陣列增加一個描述符,說明其維數、長度、邊界、元素型別等資訊。SafeArray也並不單獨使用,而是將其再包裝到VARIANT型別的變數中,然後才作為引數傳送出去。在VARIANTvt成員的值如果包含VT_ARRAY│...,那麼它所封裝的就是一個SafeArray,它的parray成員即是指向SafeArray的指標。SafeArray中元素的型別可以是VARIANT能封裝的任何型別,包括VARIANT型別本身。  使用SafeArray的具體步驟:  方法一:  包裝一個SafeArray(1). 定義變數,如:

VARIANT varChunk;
SAFEARRAY *psa;
SAFEARRAYBOUND rgsabound[1];

(2). 建立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). 放置資料元素到SafeArray

for(long index=0;index<uIsRead;index++) 
{
if(FAILED(SafeArrayPutElement(psa,&index,&bVal[index])))
::MessageBox(NULL,"出毛病了。","提示",MB_OK │ MB_ICONWARNING);
}

  一個一個地放,挺麻煩的。(4). 封裝到VARIANT內:

varChunk.vt = VT_ARRAY│VT_UI1;
varChunk.parray = psa;

  這樣就可以將varChunk作為引數傳送出去了。  讀取SafeArray中的資料的步驟:(1). SafeArrayGetElement一個一個地讀

BYTE buf[lIsRead];
for(long index=0;index<lIsRead;index++) 
{ 
::SafeArrayGetElement(varChunk.parray,&index,buf+index); 
}

  就讀到緩衝區buf裡了。  方法二:  使用SafeArrayAccessData直接讀寫SafeArray的緩衝區:(1). 讀緩衝區:

BYTE *buf;
SafeArrayAccessData(varChunk.parray, (void **)&buf);
f.Write(buf,lIsRead);
SafeArrayUnaccessData(varChunk.parray);

(2). 寫緩衝區:

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的資料緩衝區,比用SafeArrayGetElementSafeArrayPutElement速度快。特別適合於讀取資料。但用完之後不要忘了呼叫::SafeArrayUnaccessData(psa),否則會出錯的。13. 使用書籤( bookmark )  書籤可以唯一標識記錄集中的一個記錄,用於快速地將當前記錄移回到已訪問過的記錄,以及進行過濾等等。Provider會自動為記錄集中的每一條記錄產生一個書籤,我們只需要使用它就行了。我們不能試圖顯示、修改或比較書籤。ADO用記錄集的Bookmark屬性表示當前記錄的書籤。  用法步驟:(1). 建立一個VARIANT型別的變數_variant_t VarBookmark;(2). 將當前記錄的書籤值存入該變數  也就是記錄集的Bookmark屬性的當前值。VarBookmark = rst->Bookmark;(3). 返回到先前的記錄  將儲存的書籤值設定到記錄集的書籤屬性中:

// Check for whether bookmark set for a record
if (VarBookmark.vt == VT_EMPTY)
printf("No Bookmark set!\n");
else 
rst->Bookmark = VarBookmark;
  設定完後,當前記錄即會移動到該書籤指向的記錄。

14、設定過濾條件Recordset物件的Filter屬性表示了當前的過濾條件。它的值可以是以ANDOR連線起來的條件表示式(不含WHERE關鍵字)、由書籤組成的陣列或ADO提供的FilterGroupEnum列舉值。為Filter屬性設定新值後Recordset的當前記錄指標會自動移動到滿足過濾條件的第一個記錄。例如:

rst->Filter = _bstr_t ("姓名='趙薇' AND性別=’女’");

  在使用條件表示式時應注意下列問題:  (1)、可以用圓括號組成複雜的表示式  例如:

rst->Filter = _bstr_t ("(姓名='趙薇' AND性別=’女’) OR AGE<25");

  但是微軟不允許在括號內用OR,然後在括號外用AND,例如:

rst->Filter = _bstr_t ("(姓名='趙薇' OR性別=’女’) AND AGE<25");

  必須修改為:

rst->Filter = _bstr_t ("(姓名='趙薇' AND AGE<25) OR (性別=’女’ AND AGE<25)");

  (2)、表示式中的比較運算子可以是LIKELIKE後被比較的是一個含有萬用字元*的字串,星號表示若干個任意的字元。  字串的首部和尾部可以同時帶星號*

rst->Filter = _bstr_t ("姓名 LIKE '*趙*' ");

  也可以只是尾部帶星號:

rst->Filter = _bstr_t ("姓名 LIKE '趙*' ");

Filter屬性值的型別是Variant,如果過濾條件是由書籤組成的陣列,則需將該陣列轉換為SafeArray,然後再封裝到一個VARIANT_variant_t型的變數中,再賦給Filter屬性。15、索引與排序  (1)、建立索引  當以某個欄位為關鍵字用Find方法查詢時,為了加快速度可以以該欄位為關鍵字在記錄集內部臨時建立索引。只要將該欄位的Optimize屬性設定為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中稱為動態屬性),ADO本身沒有此屬性。  (2)、排序  要排序也很簡單,只要把要排序的關鍵字列表設定到Recordset物件的Sort屬性裡即可,例如:

pRstAuthors->CursorLocation = adUseClient;
pRstAuthors->Open("SELECT * FROM mytable",
_variant_t((IDispatch *) pConnection),
adOpenStatic, adLockReadOnly, adCmdText);
......
pRst->Sort = "姓名 DESC,年齡 ASC";

相關推薦

vc++ado連線資料庫的方法及詳細介紹

VC用ADO訪問資料庫全攻略,介紹了VC用ADO來訪問資料庫的各個物件及各方法,很經典,也很實用,很值得一看。   正文 一、ADO概述 ADO是Microsoft為最新和最強大的資料訪問範例 OLE DB 而設計的,是一個便於使用的應用程式層介面。ADO 使您能夠編寫應用程式以通過 OLE. DB 提供

VC++ 通過ADO連線資料庫查詢時返回空值報錯的解決方案

當資料庫的欄位值允許為空時, 而且此時內容也為空時,則執行查詢會出錯,例如 CString str = pRecordset->GetFields()->GetItem((long)0)->GetValue(); 或者 str= pRecordset-&

20160229 VC++使用ADO連線資料庫

 ADO 是目前在Windows環境中比較流行的客戶端資料庫程式設計技術。ADO是建立在OLE DB底層技術之上的高階程式設計介面,因而它兼具有強大的資料處理功能(處理各種不同型別的資料來源、分散式的資料處理等等)和極其簡單、易用的程式設計介面,因而得到了廣泛的應用。

VC++ADO方式訪問資料庫datetime欄位(不帶毫秒時間與帶毫秒時間)

    //取得列名    bstrColName = m_pRSet->GetFields()->Item[nCol]->GetName() ;    strColname = (char*)bstrColName ;    //取得當前行當前列值    varCounter.lVal =

VC++ ado連線資料庫(可以在VC6.0使用,以access資料庫為例項)(1)

很多新手對資料庫連線迷茫了,怎麼我寫的就連線不上資料庫呢?或者有些功能就實現不了(感覺這太奇葩了吧),下面就怎麼連線資料庫進行說明。 看以下程式碼和解說步驟:(後面附帶一個完整的類給大家進行下載,便於直接進行呼叫) 1.首先新建一個類,方便下次使用,編寫了一次就不用再編寫。

VC使用ADO連線Oracle資料庫詳解(含原始碼下載)

ADO 主要物件介紹     ADO物件包括:連線物件(Connection Object)、命令物件 (Command Object) 、記錄集對象(RecordSet  Object)、欄位物件(Field  Object) 、記錄物件(Record  Object)

VC使用ADO連線SQLServer資料庫(精簡實用版)

VC用ADO訪問資料庫全攻略,介紹了VC用ADO來訪問資料庫的各個物件及各方法,很經典,也很實用,很值得一看。 正文 一、ADO概述 ADO是Microsoft為最新和最強大的資料訪問範例 OLE DB 而設計的,是一個便於使用的應用程式層介面。ADO 使您能夠編寫應用程式以通過 OLE. DB 提供者訪

java開發JDBC連線資料庫的步驟

JDBC核心類 DriverManager 用於管理驅動/獲得連線 Connection 用於連線資料庫 Statement 用於執行SQL語句 ResultSet 使用者獲取執行結果,可

完整java開發JDBC連線資料庫程式碼和步驟

宣告:來自Hongten(部落格園) JDBC連線資料庫 建立一個以JDBC連線資料庫的程式,包含7個步驟: 1、載入JDBC驅動程式: 在連線資料庫之前,首先要載入想要連線的資料庫的驅動到JVM(Java虛擬機器), 這通過java.lang

Docker容器SQLyog連線資料庫報錯plugin caching_sha2_password could not be loaded

ERROR 2059 (HY000): Authentication plugin ‘caching_sha2_password’ cannot be loaded 問題: 連線Docker啟動的mysql出現:ERROR 2059 (HY000): Authentication plu

vc使用sqlite3資料庫

第5個引數char**errmsg是錯誤資訊。sqlite3裡面有很多固定的錯誤資訊。執行sqlite3_exec之後,執行失敗時可以查閱這個指標(直接printf(“%s\n”,errmsg))得到一串字串資訊,這串資訊告訴你錯在什麼地方。sqlite3_exec函式通過修改你傳入的指標的指標,把你提供的指

asp.net使用ado連線資料庫

 /// <summary>     /// 資料訪問操作類     /// chy710.cnblogs.com     /// </summary>     public class SqlDAO     {         //資料庫連線pri

在vs可以連線資料庫,獨立執行exe無法訪問的問題解決

問題: 今天寫一個與資料庫互動程式的時候,在vs裡面,直接執行,能連線上資料庫。 但直接雙擊生成的exe檔案,無法連線上資料庫。 找了半天原因,看了半天網上的文件,都沒有找到解決辦法。 原因及解決方法: 最終把連線資料庫的各個引數打印出來,才發現了問題

關於Java webservlet連線資料庫的一個細節問題

      今天在寫使用者註冊功能的程式碼時,當通過servlet接收表單資料並儲存到資料庫時,沒有出現任何錯誤,但是在資料庫中檢視,第一列使用者名稱顯示為空,無論如何檢查都感覺是對的,後來再把jsp程式碼和servlet程式碼結合著看的時候,終於發現了錯誤:      在

VC++6.0 MFC利用ADO連線到MySQL資料庫

安裝MySQL資料庫,併為其安裝驅動! 兩種連線方式:ODBC連線和非ODBC連線。 一、ODBC方式連結 1、應用程式的stdafx.h標頭檔案中(也可以在其他合適的地方)包含如下語句。 //匯入msado15.dll動態連結庫,不要名稱空間,將EOR改成adoEOR,避免與檔

【181221】VC++ ADO資料庫移動記錄集示例原始碼

原始碼下載簡介 VC++如何使用ADO在資料庫中移動記錄集呢?希望通過本例項找到答案。這是一個挺簡單的VC++初學者例項,沒有太多高難度的程式碼,純粹操作資料庫的一些基礎知識,像移動資料集,在平時的程式設計中也是經常用到的,點選窗體內的按鈕,可以向上、向下、回到第一條記錄以及回到最後一條記錄

【181225】VC++使用ADO物件新增資料至資料庫原始碼

原始碼下載簡介 VC++使用ADO物件新增資料至資料庫中,採用Access,對VC新手來說,是個挺實用的入門的資料庫例項,相關程式碼說明:   m_List.SetExtendedStyle(LVS_EX_FLATSB //扁平風格顯示滾動條   |LVS_EX_FULLROWSELECT

ADOVC的MFC下連線資料庫並插入資料

1,ADO連線資料庫 一般不用ODBC連線資料庫,太古老了,主流用ADO連線資料庫。 連線步驟: 1需要先安裝SQL SEVER2008。 啟動執行伺服器SQL SEVER(MSSQLSERVER)啟動。 登入SQL SEVER 建表字段: 上面這些都是在SQL SE

MFCADO資料庫連線

這是按照孫鑫C++視訊第二十講編寫的,但是還沒有在VS2012中找到如何得到ConnectionString的方法,待解決,多樣資料庫的連線 void CAdoDlg::OnBnClickedButton1() { // TODO: 在此新增控制元件通知處理程式程式碼

VC++通過ADO的_RecordsetPtr操作資料庫:增刪改查

_RecordsetPtr智慧指標,它是專門為通過記錄集操作資料庫而設立的指標,通過該介面可以對資料庫的表內的記錄、欄位等進行各種操作。 要搞清楚:資料庫和ADO的記錄集是兩個不同的概念,是存在於不同物理位置的兩個儲存空間。記錄集相當於是實際資料的一份拷貝。正因為記錄集是相對脫離資料庫而存在的,所以才存在後