20160229 VC++中使用ADO連線資料庫
ADO 是目前在Windows環境中比較流行的客戶端資料庫程式設計技術。ADO是建立在OLE
DB底層技術之上的高階程式設計介面,因而它兼具有強大的資料處理功能(處理各種不同型別的資料來源、分散式的資料處理等等)和極其簡單、易用的程式設計介面,因而得到了廣泛的應用。而且按微軟公司的意圖,OLE DB和ADO將逐步取代
ODBC和DAO。現在介紹ADO各種應用的文章和書籍有很多,本文著重站在初學者的角度,簡要探討一下在VC++中使用ADO程式設計時的一些問題。我們希望閱讀本文之前,您對ADO技術的基本原理有一些瞭解。一、在VC++中使用ADO程式設計ADO實際上就是由一組Automation
#import "C:\Program Files\Common Files\System\ADO\msado15.dll" \ no_namespace rename("EOF", "EndOfFile")
但要注意不能放在stdAfx.h檔案的開頭,而應該放在所有include指令的後面。否則在編譯時會出錯。程式在編譯過程中,VC++會讀出msado15.dll中的型別庫資訊,自動產生兩個該型別庫的標頭檔案和實現檔案msado15.tlh和msado15.tli(在您的Debug或Release目錄下)。在這兩個檔案裡定義了ADO的所有物件和方法,以及一些列舉型的常量等。我們的程式只要直接呼叫這些方法就行了,與使用MFC中的COleDispatchDriver類呼叫Automation物件十分類似。2、使用MFC中的CIDispatchDriver 就是通過讀取msado15.dll中的型別庫資訊,建立一個
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
SQL或Oracle等大型資料庫上程式設計,還要能熟練使用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.tlh、comdef.h和comip.h這三個檔案中找到。在msado15.tlh中有:
_COM_SMARTPTR_TYPEDEF(_Collection, __uuidof(_Collection));
經巨集擴充套件後就得到了_ConnectionPtr類。_ConnectionPtr類封裝了Connection物件的Idispatch介面指標,及一些必要的操作。我們就是通過這個指標來操縱Connection物件。類似地,後面用到的_CommandPtr和_RecordsetPtr型別也是這樣得到的,它們分別表示命令物件指標和記錄集物件的指標。 (1)、連線到MS SQL Server 注意連線字串的格式,提供正確的連線字串是成功連線到資料庫伺服器的第一步,有關連線字串的詳細資訊參見微軟MSDN Library光碟。 本例連線字串中的server_name,database_name,user_name和password在程式設計時都應該替換成實際的內容。
_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語句、一個表的名字或一個命令物件等等;第二個引數就是前面建立的連線物件的指標。此外,用Connection和Command物件的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);
}
本例中的name和age都是欄位名,讀取的欄位值分別儲存在sName和cAge變數內。例中的Fields是Recordset物件的容器,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、刪除記錄 呼叫Recordset的Delete方法就行了,刪除的是當前記錄。要了解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 );
但要注意,用Command和Connection物件的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開始的序號表示,如1,2,3等等。 特別要注意的是:如果要繫結的欄位是字串型別,則對應的字元陣列的元素個數一定要比欄位長度大2(比如m_szau_fname[22],其繫結的欄位au_fname的長度實際是20),不這樣繫結就會失敗。我分析多出的2可能是為了存放字串結尾的空字元null和BSTR字串開頭的一個字(表示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,就可以訪問。如果修改了欄位,不要忘了先呼叫picRs的Update(注意不是Recordset的Update),然後才關閉,也不要忘了釋放picRs(即picRs->Release();)。(4). 此時還可以用IADORecordBinding介面新增新紀錄
if(FAILED(picRs->AddNew(&rs)))
......
11. 訪問長資料 在Microsoft SQL中的長資料包括text、image等這樣長型別的資料,作為二進位制位元組來對待。 可以用Field物件的GetChunk和AppendChunk方法來訪問。每次可以讀出或寫入全部資料的一部分,它會記住上次訪問的位置。但是如果中間訪問了別的欄位後,就又得從頭來了。 請看下面的例子:
//寫入一張照片到資料庫:
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型別的變數中,然後才作為引數傳送出去。在VARIANT的vt成員的值如果包含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的資料緩衝區,比用SafeArrayGetElement和SafeArrayPutElement速度快。特別適合於讀取資料。但用完之後不要忘了呼叫::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屬性表示了當前的過濾條件。它的值可以是以AND或OR連線起來的條件表示式(不含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來訪問資料庫的各個物件及各方法,很經典,也很實用,很值得一看。
正文
一、ADO概述
ADO是Microsoft為最新和最強大的資料訪問範例 OLE DB 而設計的,是一個便於使用的應用程式層介面。ADO 使您能夠編寫應用程式以通過 OLE. DB 提供
當資料庫的欄位值允許為空時, 而且此時內容也為空時,則執行查詢會出錯,例如
CString str = pRecordset->GetFields()->GetItem((long)0)->GetValue();
或者
str=
pRecordset-&
ADO 是目前在Windows環境中比較流行的客戶端資料庫程式設計技術。ADO是建立在OLE
DB底層技術之上的高階程式設計介面,因而它兼具有強大的資料處理功能(處理各種不同型別的資料來源、分散式的資料處理等等)和極其簡單、易用的程式設計介面,因而得到了廣泛的應用。 //取得列名 bstrColName = m_pRSet->GetFields()->Item[nCol]->GetName() ; strColname = (char*)bstrColName ; //取得當前行當前列值 varCounter.lVal =
很多新手對資料庫連線迷茫了,怎麼我寫的就連線不上資料庫呢?或者有些功能就實現不了(感覺這太奇葩了吧),下面就怎麼連線資料庫進行說明。
看以下程式碼和解說步驟:(後面附帶一個完整的類給大家進行下載,便於直接進行呼叫)
1.首先新建一個類,方便下次使用,編寫了一次就不用再編寫。
ADO 主要物件介紹
ADO物件包括:連線物件(Connection Object)、命令物件 (Command Object) 、記錄集對象(RecordSet Object)、欄位物件(Field Object) 、記錄物件(Record Object)
VC用ADO訪問資料庫全攻略,介紹了VC用ADO來訪問資料庫的各個物件及各方法,很經典,也很實用,很值得一看。
正文
一、ADO概述
ADO是Microsoft為最新和最強大的資料訪問範例 OLE DB 而設計的,是一個便於使用的應用程式層介面。ADO 使您能夠編寫應用程式以通過 OLE. DB 提供者訪
JDBC核心類
DriverManager 用於管理驅動/獲得連線
Connection 用於連線資料庫
Statement 用於執行SQL語句
ResultSet 使用者獲取執行結果,可
宣告:來自Hongten(部落格園)
JDBC連線資料庫
建立一個以JDBC連線資料庫的程式,包含7個步驟:
1、載入JDBC驅動程式:
在連線資料庫之前,首先要載入想要連線的資料庫的驅動到JVM(Java虛擬機器),
這通過java.lang
ERROR 2059 (HY000): Authentication plugin ‘caching_sha2_password’ cannot be loaded 問題:
連線Docker啟動的mysql出現:ERROR 2059 (HY000): Authentication plu 第5個引數char**errmsg是錯誤資訊。sqlite3裡面有很多固定的錯誤資訊。執行sqlite3_exec之後,執行失敗時可以查閱這個指標(直接printf(“%s\n”,errmsg))得到一串字串資訊,這串資訊告訴你錯在什麼地方。sqlite3_exec函式通過修改你傳入的指標的指標,把你提供的指
/// <summary>
/// 資料訪問操作類
/// chy710.cnblogs.com
/// </summary>
public class SqlDAO
{
//資料庫連線pri
問題:
今天寫一個與資料庫互動程式的時候,在vs裡面,直接執行,能連線上資料庫。
但直接雙擊生成的exe檔案,無法連線上資料庫。
找了半天原因,看了半天網上的文件,都沒有找到解決辦法。
原因及解決方法:
最終把連線資料庫的各個引數打印出來,才發現了問題
今天在寫使用者註冊功能的程式碼時,當通過servlet接收表單資料並儲存到資料庫時,沒有出現任何錯誤,但是在資料庫中檢視,第一列使用者名稱顯示為空,無論如何檢查都感覺是對的,後來再把jsp程式碼和servlet程式碼結合著看的時候,終於發現了錯誤:
在
安裝MySQL資料庫,併為其安裝驅動!
兩種連線方式:ODBC連線和非ODBC連線。
一、ODBC方式連結
1、應用程式的stdafx.h標頭檔案中(也可以在其他合適的地方)包含如下語句。
//匯入msado15.dll動態連結庫,不要名稱空間,將EOR改成adoEOR,避免與檔
原始碼下載簡介
VC++如何使用ADO在資料庫中移動記錄集呢?希望通過本例項找到答案。這是一個挺簡單的VC++初學者例項,沒有太多高難度的程式碼,純粹操作資料庫的一些基礎知識,像移動資料集,在平時的程式設計中也是經常用到的,點選窗體內的按鈕,可以向上、向下、回到第一條記錄以及回到最後一條記錄
原始碼下載簡介
VC++使用ADO物件新增資料至資料庫中,採用Access,對VC新手來說,是個挺實用的入門的資料庫例項,相關程式碼說明: m_List.SetExtendedStyle(LVS_EX_FLATSB //扁平風格顯示滾動條 |LVS_EX_FULLROWSELECT
1,ADO連線資料庫
一般不用ODBC連線資料庫,太古老了,主流用ADO連線資料庫。
連線步驟:
1需要先安裝SQL SEVER2008。
啟動執行伺服器SQL SEVER(MSSQLSERVER)啟動。
登入SQL SEVER
建表字段:
上面這些都是在SQL SE
這是按照孫鑫C++視訊第二十講編寫的,但是還沒有在VS2012中找到如何得到ConnectionString的方法,待解決,多樣資料庫的連線
void CAdoDlg::OnBnClickedButton1()
{
// TODO: 在此新增控制元件通知處理程式程式碼
_RecordsetPtr智慧指標,它是專門為通過記錄集操作資料庫而設立的指標,通過該介面可以對資料庫的表內的記錄、欄位等進行各種操作。
要搞清楚:資料庫和ADO的記錄集是兩個不同的概念,是存在於不同物理位置的兩個儲存空間。記錄集相當於是實際資料的一份拷貝。正因為記錄集是相對脫離資料庫而存在的,所以才存在後 相關推薦
vc++中ado連線資料庫的方法及詳細介紹
VC++ 通過ADO連線資料庫查詢時返回空值報錯的解決方案
20160229 VC++中使用ADO連線資料庫
VC++中ADO方式訪問資料庫datetime欄位(不帶毫秒時間與帶毫秒時間)
VC++ ado連線資料庫(可以在VC6.0使用,以access資料庫為例項)(1)
VC使用ADO連線Oracle資料庫詳解(含原始碼下載)
VC使用ADO連線SQLServer資料庫(精簡實用版)
java開發中JDBC連線資料庫的步驟
完整java開發中JDBC連線資料庫程式碼和步驟
Docker容器中SQLyog連線資料庫報錯plugin caching_sha2_password could not be loaded
在vc中使用sqlite3資料庫
asp.net使用ado連線資料庫
在vs中可以連線資料庫,獨立執行exe無法訪問的問題解決
關於Java web中servlet連線資料庫的一個細節問題
VC++6.0 MFC利用ADO連線到MySQL資料庫
【181221】VC++ ADO在資料庫中移動記錄集示例原始碼
【181225】VC++使用ADO物件新增資料至資料庫中原始碼
ADO在VC的MFC下連線資料庫並插入資料
MFC中ADO資料庫連線
VC++中通過ADO中的_RecordsetPtr操作資料庫:增刪改查