軟件測試數據庫編程的總結及實例
當前各種主流數據庫有很多,包括Oracle, MS SQL Server, Sybase, Informix, MySQL, DB2, Interbase / Firebird, PostgreSQL, SQLite, SAP/DB, TimesTen, MS ACCESS等等。數據庫編程是對數據庫的創建、讀寫等一列的操作。數據庫編程分為數據庫客戶端編程與數據庫服務器端編程。數據庫客戶端編程主要使用ODBC API、ADO、ADO.NET、OCI、OTL等方法;數據庫服務端編程主要使用OLE DB等方法。數據庫編程需要掌握一些訪問數據庫技術方法,還需要注意怎么設計高效的數據庫、數據庫管理與運行的優化、數據庫語句的優化。
一、訪問數據庫技術方法
數據庫編程分為數據庫客戶端編程與數據庫服務器端編程。數據庫客戶端編程主要使用ODBC API、ADO、ADO.NET、OCI、OTL等方法;數據庫服務端編程主要使用OLE DB等方法。
1、幾種是數據庫訪問方法比較
ODBC API是一種適合數據庫底層開發的編程方法,ODBC API提供大量對數據源的操作,ODBC API能夠靈活地操作游標,支持各種幫定選項,在所有ODBC相關編程中,API編程具有最高的執行速度。
DAO提供了很好的數據庫編程的對象模型.但是,對數據庫的所有調用以及輸出的數據都必須通過Access/Jet數據庫引擎,這對于使用數據庫應用程序,是嚴重的瓶頸。
OLE DB提供了COM接口,與傳統的數據庫接口相比,有更好的健壯性和靈活性,具有很強的錯誤處理能力,能夠同非關系數據源進行通信。
ADO最主要的優點在于易于使用、速度快、內存支出少和磁盤遺跡小。
ADO.NET 是利用數據集的概念將數據庫數據讀入內存中,然后在內存中對數據進行操作,最后將數據集數據回寫到源數據庫中。
OTL 是 Oracle, Odbc and DB2-CLI Template Library 的縮寫,是一個C++編譯中操控關系數據庫的模板庫, OTL中直接操作Oracle主要是通過Oracle提供的OCI接口進行,進行操作DB2數據庫則是通過CLI接口來進行,至于MS的數據庫和其它一些數據庫,則OTL只提供了ODBC來操作的方式。當然Oracle和DB2也可以由OTL間接使用ODBC的方式來進行操縱。具有以下優點:跨平臺;運行效率高,與C語言直接調用API相當;開發效率高,起碼比ADO.net使用起來更簡單,更簡潔;部署容易,不需要ADO組件,不需要.net framework 等。
2、VC數據庫編程幾種方法
VC數據庫編程幾種方法,包括ODBC連接、MFC ODBC連接、DAO連接、OLE DB、OLE DB Templates連接、ADO、Oracle專用方法(OCI(Oracle Call Interface)訪問、Oracle Object OLE C++ Class Library )。
<1.>通用方法
1. ODBC連接
ODBC(Open DataBase Connectivity)是MSOA的一部分,是一個標準數據庫接口。它提供對關系數據庫訪問的統一接口,實現對異構數據源的一致訪問。
ODBC數據訪問由以下部分組成:
<1>句柄(Handles):ODBC使用句柄來標識ODBC環境、連接、語句和描述器.
<2>緩存區(Buffers):
<3>數據類型(Data types)
<4>一致性級別(Conformance levels)
用ODBC設計客戶端的一般步驟:
<1>分配ODBC環境
<2>分配連接句柄
<3>連接數據源
<4>構造和執行SQL語句
<5>獲得查詢結果
<6>斷開數據源的連接
<7>釋放ODBC環境
ODBC API是一種適合數據庫底層開發的編程方法,ODBC API提供大量對數據源的操作,ODBC API能夠靈活地操作游標,支持各種幫定選項,在所有ODBC相關編程中,API編程具有最高的執行速度.因此,ODBC API編程屬于底層編程。
2. MFC ODBC連接
MFC ODBC是MFC對ODBC進行的封裝,以簡化對ODBC API的 調用,從而實現面向對象的數據庫編程接口.
MFC ODBC的封裝主要開發了CDatabase類和CRecordSet類
(1) CDatabase類
CDatabase類用于應用程序建立同數據源的連接。CDatabase類中包含一個m_hdbc變量,它代表了數據源的連接句柄。如果要建立CDatabase類的實例,應先調用該類的構造函數,再調用Open函數,通過調用,初始化環境變量,并執行與數據源的連接。在通過Close函數關閉數據源。
CDatabase類提供了對數據庫進行操作的函數及事務操作。
(2) CRecordSet類
CRecordSet類定義了從數據庫接收或者發送數據到數據庫的成員變量,以實現對數據集的數據操作。
CRecordSet類的成員變量m_hstmt代表了定義該記錄集的SQL語句句柄,m_nFields為記錄集中字段的個數,m_nParams為記錄集所使用的參數個數。
CRecordSet的記錄集通過CDatabase實例的指針實現同數據源的連接,即CRecordSet的成員變量m_pDatabase.
MFC ODBC編程更適合于界面型數據庫應用程序的開發,但由于CDatabase類和CRecordSet類提供的數據庫操作函數有限,支持的游標類型也有限,限制了高效的數據庫開發。在編程層次上屬于高級編程。
應用實例:
1.打開數據庫
CDatabase database;
database.OpenEx( _T( "DSN=zhuxue" ),CDatabase::noOdbcDialog);//zhuxue為數據源名稱
2.關聯記錄集
CRecordset recset(&database);
3.查詢記錄
CString sSql1="";
sSql1 = "SELECT * FROM tablename" ;
recset.Open(CRecordset::forwardOnly, sSql1, CRecordset::readOnly);
int ti=0;
CDBVariant var;//var可以轉換為其他類型的值
while (!recset.IsEOF())
{
//讀取Excel內部數值
recset.GetFieldValue("id",var);
jiangxiang[ti].id=var.m_iVal;
recset.GetFieldValue("name", jiangxiang[ti].name);
ti++;
recset.MoveNext();
}
recset.Close();//關閉記錄集
4.執行sql語句
CString sSql="";
sSql+="delete * from 院系審核";//清空表
database.ExecuteSQL(sSql);
sSql也可以為Insert ,Update等語句
5.讀取字段名
sSql = "SELECT * FROM Sheet1" ; //讀取的文件有Sheet1表的定義,或為本程序生成的表.
// 執行查詢語句
recset.Open(CRecordset::forwardOnly, sSql, CRecordset::readOnly);
int excelColCount=recset.GetODBCFieldCount();//列數
CString excelfield[30];
//得到記錄集的字段集合中的字段的總個數
for( i=0;i<excelColCount;i++)
{
CODBCFieldInfo fieldinfo;
recset.GetODBCFieldInfo(i,fieldinfo);
excelfield[i].name =fieldinfo.m_strName;//字段名
}
6.打開excel文件
CString sDriver = "MICROSOFT EXCEL DRIVER (*.XLS)"; // Excel安裝驅動
CString sSql,sExcelFile; //sExcelFile為excel的文件路徑
TRY
{
// 創建進行存取的字符串
sSql.Format("DRIVER={%s};DSN='';FIRSTROWHASNAMES=1;READONLY=FALSE;CREATE_DB=\"%s\";DBQ=%s",sDriver, sExcelFile, sExcelFile);
// 創建數據庫 (既Excel表格文件)
if( database.OpenEx(sSql,CDatabase::noOdbcDialog) )
{
//可以把excel作為一個數據庫操作
}
}
catch(e)
{
TRACE1("Excel驅動沒有安裝: %s",sDriver);
AfxMessageBox("讀取失敗,請檢查是否定義數據區Sheet1");
}
3. DAO連接
DAO(Data Access Object)是一組Microsoft Access/Jet數據庫引擎的COM自動化接口.DAO直接與Access/Jet數據庫通信.通過Jet數據庫引擎,DAO也可以同其他數據庫進行通信。DAO還封裝了Access數據庫的結構單元,通過DAO可以直接修改Access數據庫的結構,而不必使用SQL的數據定義語言(DDL)。
DAO的體系結構如下:
DAO封裝的類:
(1)CdaoWorkspace:對DAO工作區(數據庫處理事務管理器)的封裝
(2)CdaoDatabase:對DAO數據庫對象的封裝,負責數據庫連接.
(3)CdaoRecordset:對DAO記錄集對象的封裝,代表所選的一組記錄.
(4)CdaoTableDef:對表定義對象的封裝,代表基本表或附加表定義.
(5)CdaoQueryDef:對查詢對象的封裝,包含所有查詢的定義.
(6)CdaoException:DAO用于接收數據庫操作異常的類.
(7)CDaoFieldExchange
DAO提供了很好的數據庫編程的對象模型.但是,對數據庫的所有調用以及輸出的數據都必須通過Access/Jet數據庫引擎,這對于使用數據庫應用程序,是嚴重的瓶頸。
DAO相對于ODBC來說,屬于高層的數據庫接口.
4. OLE DB連接
OLE DB對ODBC進行了兩方面的擴展:一是提供了數據庫編程的OLE接口即COM,二是提供了一個可用于關系型和非關系型數據源的接口。
OLE DB提供了COM接口,與傳統的數據庫接口相比,有更好的健壯性和靈活性,具有很強的錯誤處理能力,能夠同非關系數據源進行通信。
與ODBC API一樣,OLE DB也屬于底層的數據庫編程接口,OLE DB結合了ODBC對關系數據庫的操作功能,并進行擴展,可以訪問非關系數據庫。
OLE DB訪問數據庫的原理如下:
OLE DB程序結構:
OLE DB由客戶(Consumer)和服務器(Provider)??蛻羰鞘褂脭祿膽贸绦?,它通過OLE DB接口對數據提供者的數據進行訪問和控制。OLE DB服務器是提供OLE DB接口的軟件組件。根據提供的內容可以分為數據提供程序(Data Provider)和服務提供程序(Service Provider)。
程序結構原理圖如下:
<1>數據提供程序
數據提供程序擁有自己的數據并把數據以表格的形式呈現給使用者使用.
<2>服務提供程序
服務提供程序是數據提供程序和使用者的結合。它是OLE DB體系結構中的中間件,它是OLE DB數據源的使用者和數據使用程序的提供者
<3>數據使用程序
數據使用程序對存儲在數據提供程序中的數據進行使用和控制.
OLE DB開發程序的一般步驟:
<1>初始化COM環境
<2>連接數據源
<3>打開對話
<4>執行命令
<5>處理結果
<6>清除對象
應用實例:
使用OLEDB編寫數據庫應用程序
1 概述
OLE DB的存在為用戶提供了一種統一的方法來訪問所有不同種類的數據源。OLE DB可以在不同的數據源中進行轉換。利用OLE DB,客戶端的開發人員在進行數據訪問時只需把精力集中在很少的一些細節上,而不必弄懂大量不同數據庫的訪問協議。
OLE DB是一套通過COM接口訪問數據的ActiveX接口。這個OLE DB接口相當通用,足以提供一種訪問數據的統一手段,而不管存儲數據所使用的方法如何。同時,OLE DB還允許開發人員繼續利用基礎數據庫技術的優點,而不必為了利用這些優點而把數據移出來。
2 使用ATL使用OLE DB數據使用程序
由于直接使用OLE DB的對象和接口設計數據庫應用程序需要書寫大量的代碼。為了簡化程序設計,Visual C++提供了ATL模板用于設計OLE DB數據應用程序和數據提供程序。
利用ATL模板可以很容易地將OLE DB與MFC結合起來,使數據庫的參數查詢等復雜的編程得到簡化。MFC提供的數據庫類使OLE DB的編程更具有面向對象的特性。Viual C++所提供用于OLE DB的ATL模板可分為數據提供程序的模板和數據使用程序的模板。
使用ATL模板創建數據應用程序一般有以下幾步驟:
1)、 創建應用框架
2)、 加入ATL產生的模板類
3)、 在應用中使用產生的數據訪問對象
3 不用ATL使用OLE DB數據使用程序
利用ATL模板產生數據使用程序較為簡單,但適用性不廣,不能動態適應數據庫的變化。下面我們介紹直接使用MFC OLE DB類來生成數據使用程序。
模板的使用
OLE DB數據使用者模板是由一些模板組成的,包括如下一些模板,下面對一些常用類作一些介紹。
1)、 會話類
CDataSource類
CDataSource類與OLE DB的數據源對象相對應。這個類代表了OLE DB數據提供程序和數據源之間的連接。只有當數據源的連接被建立之后,才能產生會話對象,可以調用Open來打開數據源的連接。
CSession類
CSession所創建的對象代表了一個單獨的數據庫訪問的會話。一個用CDataSource類產生的數據源對象可以創建一個或者多個會話,要在數據源對象上產生一個會話對象,需要調用函數Open()來打開。同時,會話對象還可用于創建事務操作。
CEnumeratorAccessor類
CEnumeratorAccessor類是用來訪問枚舉器查詢后所產生的行集中可用數據提供程序的信息的訪問器,可提供當前可用的數據提供程序和可見的訪問器。
2)、 訪問器類
CAcessor類
CAccessor類代表與訪問器的類型。當用戶知道數據庫的類型和結構時,可以使用此類。它支持對一個行集采用多個訪問器,并且,存放數據的緩沖區是由用戶分配的。
CDynamicAccessor類
CDynamicAccessor類用來在程序運行時動態的創建訪問器。當系統運行時,可以動態地從行集中獲得列的信息,可根據此信息動態地創建訪問器。
CManualAccessor類
CManualAccessor類中以在程序運行時將列與變量綁定或者是將參數與變量捆定。
3)、 行集類
CRowSet類
CRowSet類封裝了行集對象和相應的接口,并且提供了一些方法用于查詢、設置數據等??梢杂肕ove()等函數進行記錄移動,用GetData()函數讀取數據,用Insert()、Delete()、SetData()來更新數據。
CBulkRowset類
CBulkRowset類用于在一次調用中取回多個行句柄或者對多個行進行操作。
CArrayRowset類
CArrayRowset類提供用數組下標進行數據訪問。
4)、 命令類
CTable類
CTable類用于對數據庫的簡單訪問,用數據源的名稱得到行集,從而得到數據。
CCommand類
CCommand類用于支持命令的數據源??梢杂肙pen()函數來執行SQL命令,也可以Prepare()函數先對命令進行準備,對于支持命令的數據源,可以提高程序的靈活性和健壯性。
在stdafx.h頭文件里,加入如下代碼。
#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>
#include <atldbcli.h>
#include <atldbsch.h> // if you are using schema templates
在stdafx.cpp文件里,加入如下代碼。
#include <atlimpl.cpp>
CComModule _Module;
決定使用何種類型的存取程序和行集。
獲取數據
在打開數據源,會話,行集對象后就可以獲取數據了。所獲取的數據類型取決于所用的存取程序,可能需要綁定列。按以下步驟。
1、 用正確的命令打開行集對象。
2、 如果使用CManualAccessor,在使用之前與相應列進行綁定。要綁定列,可以用函數GetColumnInfo,如下所示:
// Get the column information
ULONG ulColumns = 0;
DBCOLUMNINFO* pColumnInfo = NULL;
LPOLESTR pStrings = NULL;
if (rs.GetColumnInfo(&ulColumns, &pColumnInfo, &pStrings) != S_OK)
AfxThrowOLEDBException(rs.m_pRowset, IID_IColumnsInfo);
struct MYBIND* pBind = new MYBIND[ulColumns];
rs.CreateAccessor(ulColumns, &pBind[0], sizeof(MYBIND)*ulColumns);
for (ULONG l=0; l<ulColumns; l++)
rs.AddBindEntry(l+1, DBTYPE_STR, sizeof(TCHAR)*40, &pBind[l].szValue, NULL, &pBind[l].dwStatus);
rs.Bind();
3、 用while循環來取數據。在循環中,調用MoveNext來測試光標的返回值是否為S_OK,如下所示:
while (rs.MoveNext() == S_OK)
{
// Add code to fetch data here
// If you are not using an auto accessor, call rs.GetData()
}
4、 在while循環內,可以通過不同的存取程序獲取數據。
1) 如果使用的是CAccessor類,可以通過使用它們的數據成員進行直接訪問。如下所示:
2) 如果使用的是CDynamicAccessor 或CDynamicParameterAccessor 類,可以通過GetValue或GetColumn函數來獲取數據??梢杂肎etType來獲取所用數據類型。如下所示:
while (rs.MoveNext() == S_OK)
{
// Use the dynamic accessor functions to retrieve your
// data
ULONG ulColumns = rs.GetColumnCount();
for (ULONG i=0; i<ulColumns; i++)
{
rs.GetValue(i);
}
}
3) 如果使用的是CManualAccessor,可以指定自己的數據成員,綁定它們。就可以直接存取。如下所示:
while (rs.MoveNext() == S_OK)
{
// Use the data members you specified in the calls to
// AddBindEntry.
wsprintf("%s", szFoo);
}
決定行集的數據類型
在運行時決定數據類型,要用動態或手工的存取程序。如果用的是手工存取程序,可以用GetColumnInfo函數得到行集的列信息。從這里可以得到數據類型。
4 總結
由于現在有多種數據源,,想要對這些數據進行訪問管理的唯一途徑就是通過一些同類機制來實現,如OLE DB。高級OLE DB結構分成兩部分:客戶和提供者??蛻羰褂糜商峁┱呱傻臄祿?。
就像其它基于COM的多數結構一樣,OLE DB的開發人員需要實現很多的接口,其中大部分是模板文件。
當生成一個客戶對象時,可以通過ATL對象向導指向一個數據源而創建一個簡單的客戶。ATL對象向導將會檢查數據源并創建數據庫的客戶端代理。從那里,可以通過OLE DB客戶模板使用標準的瀏覽函數。
當生成一個提供者時,向導提供了一個很好的開端,它們僅僅是生成了一個簡單的提供者來列舉某一目錄下的文件。然后,提供者模板包含了OLE DB支持的完全補充內容。在這種支持下,用戶可以創建OLE DB提供者,來實現行集定位策略、數據的讀寫以及建立書簽。
應用案例:
Visual C++中使用OLE DB讀寫SQL Server
在需要對數據庫進行操作時,OLE DB總是被認為是一種效率最高但最難的方法。但是以我最近使用OLE DB的經驗看來,OLE DB的效率高則高矣,但卻一點都不難。說它難恐怕主要是因為可參考的中文資料太少,為了幫助以后需要接觸OLE DB的同行,我撰寫了這篇文章。本文包含如下內容:
1. OLE DB寫數據庫;
2. OLE DB讀數據庫;
3. OLE DB對二進制數據(text、ntext、image等)的處理。
首先來看看對SQL Server進行寫操作的代碼,有一定VC基礎的讀者應該可以很順利地看懂。OLE DB寫數據庫,就是這么簡單!
注:
1.以下代碼中使用的模板類EAutoReleasePtr<T>與ATL中的CComPtr<T>類似,是一個在析構時自動調用Release的類。CComPtr<T>的代碼在ATLBASE.H中定義。
2.以下代碼均在UNICODE環境下編譯,因為執行的SQL語句必須是UNICODE的。設置工程為UNICODE的方法是:首先在project->settings->C/C++的屬性頁中的Preprocessor中,刪除_MBCS寫入UNICODE,_UNICODE。然后在link屬性頁中Category中選擇output,在Entry-Point symbol 中添加wWinMainCRTStartup。
EAutoReleasePtr<IDBInitialize> pIDBInitialize;
HRESULT hResult = ConnectDatabase( &pIDBInitialize, _T("127.0.0.1"), _T(“sa”), _T("password") );
if( FAILED( hResult ) )
{
//失敗,可能是因為數據庫沒有啟動、用戶名密碼錯等等
return;
}
EAutoReleasePtr<IOpenRowset> pIOpenRowset;
hResult = CreateSession( pIDBInitialize, &pIOpenRowset );
if( FAILED( hResult ) )
{
//出錯
return;
}
EAutoReleasePtr<ICommand> pICommand;
EAutoReleasePtr<ICommandText> pICommandText;
hResult = CreateCommand( pIOpenRowset, &pICommand, &pICommandText );
if( FAILED( hResult ) )
{
//出錯
return;
}
hResult = ExecuteSQL( pICommand, pICommandText, _T("USE PBDATA") );
if( FAILED( hResult ) )
{
//如果這里失敗,那就是SQL語句執行失敗。在此處,就是PBDATA還未創建
return;
}
// 創建表
ExecuteSQL( pICommand, pICommandText, _T("CREATE TABLE 2005_1(Volume real NOT NULL,ID int NOT NULL IDENTITY)") );
// 添加記錄
ExecuteSQL( pICommand, pICommandText, _T("INSERT INTO 2005_1 VALUES(100.0)") );
//...
其中幾個函數的代碼如下:
HRESULT ConnectDatabase( IDBInitialize** ppIDBInitialize, LPCTSTR pszDataSource, LPCTSTR pszUserID, LPCTSTR pszPassword )
{
ASSERT( ppIDBInitialize != NULL && pszDataSource != NULL && pszUserID != NULL && pszPassword != NULL );
UINT uTimeout = 15U; // 連接數據庫超時(秒)
TCHAR szInitStr[1024];
VERIFY( 1023 >= wsprintf( szInitStr, _T("Provider=SQLOLEDB;Data Source=%s;Initial Catalog=master;User Id=%s;Password=%s;Connect Timeout=%u"), pszDataSource, pszUserID, pszPassword, uTimeout ) );
//Initial Catalog=master指明連接成功后,"USE master"。
EAutoReleasePtr<IDataInitialize> pIDataInitialize;
HRESULT hResult = ::CoCreateInstance( CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER,
IID_IDataInitialize, ( void** )&pIDataInitialize );
if( FAILED( hResult ) )
{
return hResult;
}
EAutoReleasePtr<IDBInitialize> pIDBInitialize;
hResult = pIDataInitialize->GetDataSource( NULL, CLSCTX_INPROC_SERVER, ( LPCOLESTR )szInitStr,
IID_IDBInitialize, ( IUnknown** )&pIDBInitialize );
if( FAILED( hResult ) )
{
return hResult;
}
hResult = pIDBInitialize->Initialize( );
if( FAILED( hResult ) )
{
return hResult;
}
* ppIDBInitialize = pIDBInitialize.Detach( );
return S_OK;
}
HRESULT CreateSession( IDBInitialize* pIDBInitialize, IOpenRowset** ppIOpenRowset )
{
ASSERT( pIDBInitialize != NULL && ppIOpenRowset != NULL );
EAutoReleasePtr<IDBCreateSession> pSession;
HRESULT hResult = pIDBInitialize->QueryInterface( IID_IDBCreateSession, ( void** )&pSession );
if( FAILED( hResult ) )
{
return hResult;
}
EAutoReleasePtr<IOpenRowset> pIOpenRowset;
hResult = pSession->CreateSession( NULL, IID_IOpenRowset, ( IUnknown** )&pIOpenRowset );
if( FAILED( hResult ) )
{
return hResult;
}
* ppIOpenRowset = pIOpenRowset.Detach( );
return S_OK;
}
HRESULT CreateCommand( IOpenRowset* pIOpenRowset, ICommand** ppICommand, ICommandText** ppICommandText )
{
ASSERT( pIOpenRowset != NULL && ppICommand != NULL && ppICommandText != NULL );
HRESULT hResult;
EAutoReleasePtr<ICommand> pICommand;
{
EAutoReleasePtr<IDBCreateCommand> pICreateCommand;
hResult = pIOpenRowset->QueryInterface( IID_IDBCreateCommand, ( void** )&pICreateCommand );
if( FAILED( hResult ) )
{
return hResult;
}
hResult = pICreateCommand->CreateCommand( NULL, IID_ICommand, (IUnknown**)&pICommand );
if( FAILED( hResult ) )
{
return hResult;
}
}
EAutoReleasePtr<ICommandText> pICommandText;
hResult = pICommand->QueryInterface( &pICommandText );
if( FAILED( hResult ) )
{
return hResult;
}
* ppICommand = pICommand.Detach( );
* ppICommandText = pICommandText.Detach( );
return S_OK;
}
HRESULT ExecuteSQL( ICommand* pICommand, ICommandText* pICommandText, LPCTSTR pszCommand, LONG* plRowsAffected )
{
ASSERT( pICommand != NULL && pICommandText != NULL && pszCommand != NULL && pszCommand[0] != 0 );
HRESULT hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )pszCommand );
if( FAILED( hResult ) )
{
return hResult;
}
LONG lAffected;
hResult = pICommand->Execute( NULL, IID_NULL, NULL, plRowsAffected == NULL ? &lAffected : plRowsAffected, ( IUnknown** )NULL );
return hResult;
}
以上就是寫數據庫的全部代碼了,是不是很簡單呢?下面再來讀的。
// 先用與上面代碼中一樣的步驟獲取pICommand,pICommandText。此處省略
HRESULT hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )_T("SELECT Volume FROM 2005_1 WHERE ID = @@IDENTITY") ); //取我們剛剛添加的那一條記錄
if( FAILED( hResult ) )
{
return;
}
LONG lAffected;
EAutoReleasePtr<IRowset> pIRowset;
hResult = pICommand->Execute( NULL, IID_IRowset, NULL, &lAffected, ( IUnknown** )&pIRowset );
if( FAILED( hResult ) )
{
return;
}
EAutoReleasePtr<IAccessor> pIAccessor;
hResult = pIRowset->QueryInterface( IID_IAccessor, ( void** )&pIAccessor );
if( FAILED( hResult ) )
{
return;
}
// 一個根據表中各字段的數值類型而定義的結構,用于存儲返回的各字段的值
struct CLoadLastFromDB
{
DBSTATUS dwdsVolume;
DWORD dwLenVolume;
float fVolume;
};
// 此處我們只查詢了一個字段。如果要查詢多個字段,CLoadLastFromDB中要添加相應的字段定義,下面的dbBinding也要相應擴充。dbBinding[].iOrdinal要分別指向各個字段,dbBinding[].wType要根據字段類型賦合適的值。
DBBINDING dbBinding[1];
dbBinding[0].iOrdinal = 1; // Volume 字段的位置,從 1 開始
dbBinding[0].obValue = offsetof( CLoadLastFromDB, fVolume );
dbBinding[0].obLength = offsetof( CLoadLastFromDB, dwLenVolume );
dbBinding[0].obStatus = offsetof( CLoadLastFromDB, dwdsVolume );
dbBinding[0].pTypeInfo = NULL;
dbBinding[0].pObject = NULL;
dbBinding[0].pBindExt = NULL;
dbBinding[0].dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
dbBinding[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
dbBinding[0].eParamIO = DBPARAMIO_NOTPARAM;
dbBinding[0].cbMaxLen = 0;
dbBinding[0].dwFlags = 0;
dbBinding[0].wType = DBTYPE_R4; // float就是DBTYPE_R4,int就是DBTYPE_I4。參見MSDN
dbBinding[0].bPrecision = 0;
dbBinding[0].bScale = 0;
HACCESSOR hAccessor = DB_NULL_HACCESSOR;
DBBINDSTATUS dbs[1];
hResult = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, 1, dbBinding, sizeof( CLoadLastDataFromDB ), &hAccessor, dbs );
if( FAILED( hResult ) )
{
return;
}
ASSERT( dbs[0] == DBBINDSTATUS_OK );
ULONG uRowsObtained = 0;
HROW hRows[1]; // 這里我們只查詢了最新的那一條記錄
HROW* phRows = hRows;
CLoadLastFromDB rmd;
hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );
if( SUCCEEDED( hResult ) && uRowsObtained != 0U )
{
hResult = pIRowset->GetData( phRows[0], hAccessor, &rmd );
if( FAILED( hResult ) )
{
ASSERT( FALSE );
}
ASSERT( rmd.dwdsVolume == DBSTATUS_S_OK );
// rmd.fVolume 就是我們要取的值
}
pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );
pIAccessor->ReleaseAccessor( hAccessor, NULL );
pIAccessor.Release( );
pIRowset.Release( );
讀操作也完成了,是不是仍然很簡單呢?下面我們再來看看最麻煩的二進制數據(text、ntext、image等)的讀寫。要實現BLOB數據的讀寫,我們需要一個輔助的類,定義如下:
class CSequentialStream : public ISequentialStream // BLOB 數據訪問類
{
public:
CSequentialStream( );
virtual ~CSequentialStream( );
virtual BOOL Seek( ULONG uPosition );
virtual BOOL Clear( );
virtual ULONG GetLength( ) { return m_uBufferUsed; };
virtual operator void* const( ) { return m_pBuffer; };
STDMETHODIMP_( ULONG ) AddRef( ) { return ++ m_uRefCount; };
STDMETHODIMP_( ULONG ) Release( ) { ASSERT( m_uRefCount != 0U ); -- m_uRefCount; if( m_uRefCount == 0U ) { delete this; } return m_uRefCount; };
STDMETHODIMP QueryInterface( REFIID riid, LPVOID* ppv );
STDMETHODIMP Read( void __RPC_FAR* pv, ULONG cb, ULONG __RPC_FAR* pcbRead );
STDMETHODIMP Write( const void __RPC_FAR* pv, ULONG cb, ULONG __RPC_FAR* pcbWritten );
void ResetPosition( ) { m_uPosition = 0U; };
HRESULT PreAllocBuffer( ULONG uSize );
private:
ULONG m_uRefCount; // reference count
void* m_pBuffer; // buffer
ULONG m_uBufferUsed; // buffer used
ULONG m_uBufferSize; // buffer size
ULONG m_uPosition; // current index position in the buffer
};
實現如下:
CSequentialStream::CSequentialStream( ) : m_uRefCount( 0U ), m_pBuffer( NULL ), m_uBufferUsed( 0U ), m_uBufferSize( 0U ), m_uPosition( 0U )
{
AddRef( );
}
CSequentialStream::~CSequentialStream( )
{
Clear( );
}
HRESULT CSequentialStream::QueryInterface( REFIID riid, void** ppv )
{
if( riid == IID_IUnknown || riid == IID_ISequentialStream )
{
* ppv = this;
( ( IUnknown* )*ppv )->AddRef( );
return S_OK;
}
* ppv = NULL;
return E_NOINTERFACE;
}
BOOL CSequentialStream::Seek( ULONG uPosition )
{
ASSERT( uPosition < m_uBufferUsed );
m_uPosition = uPosition;
return TRUE;
}
BOOL CSequentialStream::Clear( )
{
m_uBufferUsed = 0U;
m_uBufferSize = 0U;
m_uPosition = 0U;
( m_pBuffer != NULL ? CoTaskMemFree( m_pBuffer ) : 0 );
m_pBuffer = NULL;
return TRUE;
}
HRESULT CSequentialStream::PreAllocBuffer( ULONG uSize )
{
if( m_uBufferSize < uSize )
{
m_uBufferSize = uSize;
m_pBuffer = CoTaskMemRealloc( m_pBuffer, m_uBufferSize );
if( m_pBuffer == NULL )
{
Clear( );
return STG_E_INSUFFICIENTMEMORY;
}
}
return S_OK;
}
HRESULT CSequentialStream::Read( void* pv, ULONG cb, ULONG* pcbRead )
{
( pcbRead != NULL ? ( * pcbRead = 0U ) : 0 );
if( pv == NULL ) { return STG_E_INVALIDPOINTER; }
if( cb == 0U ) { return S_OK; }
ASSERT( m_uPosition <= m_uBufferUsed );
ULONG uBytesLeft = m_uBufferUsed - m_uPosition;
if( uBytesLeft == 0U ) { return S_FALSE; } //no more bytes
ULONG uBytesRead = ( cb > uBytesLeft ? uBytesLeft : cb );
memcpy( pv, ( BYTE* )m_pBuffer + m_uPosition, uBytesRead );
m_uPosition += uBytesRead;
( pcbRead != NULL ? ( * pcbRead = uBytesRead ) : 0 );
return ( cb != uBytesRead ? S_FALSE : S_OK );
}
HRESULT CSequentialStream::Write( const void* pv, ULONG cb, ULONG* pcbWritten )
{
if( pv == NULL ) { return STG_E_INVALIDPOINTER; }
( pcbWritten != NULL ? ( * pcbWritten = 0U ) : 0 );
if( cb == 0U ){ return S_OK; }
ASSERT( m_uPosition <= m_uBufferUsed );
if( m_uBufferSize < m_uPosition + cb )
{
m_uBufferSize = m_uPosition + cb;
m_pBuffer = CoTaskMemRealloc( m_pBuffer, m_uBufferSize );
if( m_pBuffer == NULL )
{
Clear( );
return STG_E_INSUFFICIENTMEMORY;
}
}
m_uBufferUsed = m_uPosition + cb;
memcpy( ( BYTE* )m_pBuffer + m_uPosition, pv, cb );
m_uPosition += cb;
( pcbWritten != NULL ? ( * pcbWritten = cb ) : 0 );
return S_OK;
}
下面我們開始往一個包含ntext字段的表中添加記錄。假設這個表(News)的結構為:ID int NOT NULL IDENTITY、Title nchar(80)、 Contents ntext。
// 先將記錄添加進去,ntext字段留空。我們稍后再更新ntext的內容。
HRESULT hResult = ExecuteSQL( pICommand, pICommandText, _T("INSERT INTO News VALUES('TEST','')") );
DBPROP dbProp;
dbPropSet.guidPropertySet = DBPROPSET_ROWSET;
dbPropSet.cProperties = 1;
dbPropSet.rgProperties = &dbProp;
DBPROPSET dbPropSet;
dbPropSet.rgProperties[0].dwPropertyID = DBPROP_UPDATABILITY;
dbPropSet.rgProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;
dbPropSet.rgProperties[0].dwStatus = DBPROPSTATUS_OK;
dbPropSet.rgProperties[0].colid = DB_NULLID;
dbPropSet.rgProperties[0].vValue.vt = VT_I4;
V_I4( &dbPropSet.rgProperties[0].vValue ) = DBPROPVAL_UP_CHANGE;
EAutoReleasePtr<ICommandProperties> pICommandProperties;
hResult = pICommandText->QueryInterface( IID_ICommandProperties, ( void** )&pICommandProperties );
// 設置 Rowset 屬性為“可以更新某字段的值”
hResult = pICommandProperties->SetProperties( 1, &dbPropSet );
hResult = pICommandText->SetCommandText( DBGUID_DBSQL, ( LPCOLESTR )L"SELECT Contents FROM News WHERE ID = @@IDENTITY" );
LONG lAffected;
EAutoReleasePtr<IRowsetChange> pIRowsetChange;
hResult = pICommand->Execute( NULL, IID_IRowsetChange, NULL, &lAffected, ( IUnknown** )&pIRowsetChange );
EAutoReleasePtr<IAccessor> pIAccessor;
hResult = pIRowsetChange->QueryInterface( IID_IAccessor, ( void** )&pIAccessor );
struct BLOBDATA
{
DBSTATUS dwStatus;
DWORD dwLength;
ISequentialStream* pISeqStream;
};
// 有關DBOBJECT、DBBINDING的設置,建議參考MSDN,很容易懂。
DBOBJECT dbObj;
dbObj.dwFlags = STGM_READ;
dbObj.iid = IID_ISequentialStream;
DBBINDING dbBinding;
dbBinding.iOrdinal = 1; // BLOB 字段的位置,從 1 開始
dbBinding.obValue = offsetof( BLOBDATA, pISeqStream );
dbBinding.obLength = offsetof( BLOBDATA, dwLength );
dbBinding.obStatus = offsetof( BLOBDATA, dwStatus );
dbBinding.pTypeInfo = NULL;
dbBinding.pObject = &dbObj;
dbBinding.pBindExt = NULL;
dbBinding.dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
dbBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED;
dbBinding.eParamIO = DBPARAMIO_NOTPARAM;
dbBinding.cbMaxLen = 0;
dbBinding.dwFlags = 0;
dbBinding.wType = DBTYPE_IUNKNOWN;
dbBinding.bPrecision = 0;
dbBinding.bScale = 0;
HACCESSOR hAccessor = DB_NULL_HACCESSOR;
DBBINDSTATUS dbs;
hResult = pIAccessor->CreateAccessor( DBACCESSOR_ROWDATA, 1, &dbBinding, sizeof( BLOBDATA ), &hAccessor, &dbs );
EAutoReleasePtr<IRowset> pIRowset;
hResult = pIRowsetChange->QueryInterface( IID_IRowset, ( void** )&pIRowset );
ULONG uRowsObtained = 0;
HROW* phRows = NULL;
hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );
CSequentialStream* pss = new CSequentialStream;
pss->PreAllocBuffer( 1024 ); // 預先分配好內存,并讀入數據
pss->Write( pszSomebuffer, 512, NULL ); // pss->Write可以連續調用
pss->Write( pszSomebuffer+512, 512, NULL );
pss->ResetPosition( );
BLOBDATA bd;
bd.pISeqStream = ( ISequentialStream* )pss;
bd.dwStatus = DBSTATUS_S_OK;
bd.dwLength = pss->GetLength( );
// 將 BLOB 數據寫入到數據庫
hResult = pIRowsetChange->SetData( phRows[0], hAccessor, &bd );
pIAccessor->ReleaseAccessor( hAccessor, NULL );
pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );
// pss was released by pIRowsetChange->SetData.
這樣,我們就完成了一條記錄的添加。讀取BLOB字段的代碼跟上面的完全類似,只要把
hResult = pIRowset->GetNextRows( NULL, 0, 1, &uRowsObtained, &phRows );
后面的那些改成下面的代碼即可。
BLOBDATA bd;
hResult = pIRowset->GetData( phRows[0], hAccessor, &bd );
if( bd.dwStatus == DBSTATUS_S_ISNULL )
{
// 此字段為空
}
else if( bd.dwStatus != DBSTATUS_S_OK || bd.pISeqStream == NULL )
{
// 失敗
}
else
{
// 從系統分配的 ISequentialStream 接口讀入 BLOB 數據
BYTE szReadBuffer[1024];
for( ULONG uRead = 0U; ; )
{
if( FAILED( bd.pISeqStream->Read( szReadBuffer, 1024, &uRead ) ) )
{
break;
}
//szReadBuffer中就包含了BLOB字段的數據
if( uRead != 1024 )
{
break;
}
}
bd.pISeqStream->Release( );
}
pIAccessor->ReleaseAccessor( hAccessor, NULL );
pIRowset->ReleaseRows( uRowsObtained, phRows, NULL, NULL, NULL );
5. OLE DB Templates連接
使用OLE DB接口編程屬于最低可能層,代碼冗長并且很難維護。因此MS Visual Studio對OLE DB進一步抽象和封裝,提供COM OLE DB Templates這個可行的中間層,從而簡化了OLE DB應用程序的編寫。
OLE DB Templates編寫客戶數據庫程序方法:
<1>以MFC AppWizard為向導建立應用程序框架,添加OLE DB支持的頭文件,然后使用OLE DB類進行數據庫應用開發。
<2>以ATL COM AppWizard為向導建立應用程序框架,該框架直接支持OLE DB模板類。
OLE DB Templates包括:Consumer Templates和Provider Templates。
(1) Consumer Templates使用者模板
使用者模板(Consumer Templates)體系結構:
(2) Provider Templates服務器模板
服務器模板類體系結構:
原文轉自:http://www.anti-gravitydesign.com