資料庫及其編程API來源於不同的背景,開發人員可以從眾多的資料庫中選擇一種,每種資料庫都有自己的一套編程API,這就為資料庫編程造成了很大的局限性。SQL是標準化資料庫編程介面的一種嘗試,然而各種資料庫所支援的SQL又有所不同。
ODBC的設計目的是允許訪問多種資料庫,ODBC為資料庫供應商提供了一致的ODBC驅動程式標準,遵循這個標準開發的資料庫驅動程式,都可以被開發人員通過ODBC API透明地訪問,而不必關心實際的資料庫是什麼。在這裡,ODBC所做的,就是接收開發人員的資料庫操作指令,調用相應的ODBC驅動程式,向一個資料庫或者向多個資料庫發送資料,並可接收來自資料庫的資料。
ODBC提供了訪問資料庫的標準,這使得開發人員將精力集中在應用程式以及使用者介面的開發上,而不必考慮與之相連的資料庫。為了進一步簡化開發工作,ODBC把API層實現為SQL的映射器。通常資料庫開發人員將標準的SQL語句發送給ODBC驅動程式,再由ODBC驅動程式將這個SQL語句映射成資料庫可以支援的SQL語句。
本章首先對ODBC API的概貌進行簡要介紹,然後講述利用ODBC API進行資料庫開發的技巧,最後將通過具體資料庫開發執行個體,詳細講述通過ODBC API開發資料庫應用程式的方法和過程。
5.1 瞭解ODBC API
ODBC是一種使用SQL的程式設計介面。使用ODBC讓應用程式的編寫者避免了與資料來源相聯的複雜性。這項技術目前已經得到了大多數DBMS廠商們的廣泛支援。ODBC是一種使用SQL 的程式設計介面。使用ODBC讓應用程式的編寫者避免了與資料來源相聯的複雜性。這項技術目前已經得到了大多數DBMS廠商們的廣泛支援。
Microsoft Developer Studio為大多數標準的資料庫格式提供了32位ODBC磁碟機。這些標準資料格式包括有:SQL Server,Access,Paradox,dBase,FoxPro,Excel,Oracle 以及Microsoft Text。如果使用者希望使用其他資料格式,使用者需要相應的ODBC磁碟機及DBMS。
ODBC API是一個內容豐富的資料庫編程介面,包括60多個函數、SQL資料類型以及常量的聲明。ODBC API 是獨立於DBMS和作業系統的,而且它與程式設計語言無關。ODBC API 以X/Open和ISO/IEC中的CLI規範為基礎,ODBC 3.0完全實現了這兩種規範,並添加了基於視圖的資料庫應用程式開發人員所需要的共同特性,例如可滾動游標。ODBC API中的函數由特定DBMS驅動程式的開發人員實現,應用程式用這些驅動程式調用函數,以獨立於DBMS的方式訪問資料。
ODBC API涉及了資料來源串連與管理、結果集檢索、資料庫管理、資料繫結、事務操作等內容,目前的最高版本是3.0。
5.2 ODBC API編程步驟
通常使用ODBC API開發資料庫應用程式需要經過如下步驟:
" 串連資料來源。
" 分配語句控制代碼。
" 準備並執行SQL語句。
" 擷取結果集。
" 提交事務。
" 斷開資料來源串連並釋放環境控制代碼。
下面對上述步驟做詳細的介紹。
5.2.1 步驟1:串連資料來源
為了串連資料來源,必須要建立一個資料來源串連的環境控制代碼。通過調用SQLAllocEnv函數實現對環境控制代碼的分配,在ODBC 3.0裡,這個函數已經被SQLAllocHandle取代,但是熟悉ODBC API的開發人員還是習慣用這個函數建立環境控制代碼,因為VC++開發平台有一個映射服務,這個服務將程式碼對函數SQLAllocEnv的調用轉向對函數SQLAllocHandle的調用。
這裡有必要對"環境控制代碼"這個概念進行說明。控制代碼是指向一個特殊結構的指標,而環境指的是驅動程式管理器需要為該驅動程式儲存的有關係統和資料來源的一般資訊。由於這個時候還沒有建立同資料來源的串連,驅動程式還並不知道該使用哪一個驅動程式來完成這個任務,所以這個任務只能由驅動程式管理器來完成,利用這個環境控制代碼保留資訊直到被使用。
使用函數SQLAllocEnv建立環境控制代碼的文法如下:
HENV henv;
RETCODE rcode;
rcode = ::SQLAllocEnv(SQL_HANDLE_ENV, SQL_NULL, & henv);
if(rcode == SQL_SUCCESS) // 環境控制代碼建立成功
{
// 執行其它操作
…………
}
完成了環境控制代碼的建立以後,還要建立一個串連控制代碼。串連控制代碼的建立函數是SQLAllocConnect,其調用文法如下:
HDBC hdbc;
retcode = ::SQLAllocConnect( m_henv, & hdbc);
if(rcode == SQL_SUCCESS) // 串連控制代碼建立成功
{
// 執行其它操作
…………
}
完成了環境控制代碼和串連控制代碼的建立以後,就可以進行實際的資料來源串連了。完成資料來源串連的函數是SQLConnect,其調用文法如下:
m_retcode = :: SQLConnect( m_hdbc,
(PUCHAR)pszSourceName, SQL_NTS,
(PUCHAR)pszUserId, wLengthUID,
(PUCHAR)pszPassword, wLengthPSW );
if(rcode == SQL_SUCCESS) // 資料來源串連成功
{
// 執行其它操作
…………
}
到此,應用程式同資料來源的串連已經完成。
有些時候,ODBC資料來源並不是事先在使用者的電腦裡安裝好了的,這時就需要應用程式能夠動態建立ODBC資料來源。ODBC API提供了動態建立資料來源的函數SQLConfigDataSource。該函數的文法如下:
BOOL SQLConfigDataSource(HWND hwndParent,
WORD fRequest,
LPCSTR lpszDriver,
LPCSTR lpszAttributes);
參數hwndParent用於指定父視窗控制代碼,在不需要顯示建立資料來源對話方塊時,可以將該參數指定為NULL;參數fRequest用於指定函數的操作內容,函數SQLConfigDataSource能夠實現的操作內容由參數fRequest制定,參數fRequest取值如下:
ODBC_ADD_DSN:建立資料來源;
ODBC_CONFIG_DSN:配置或者修改已經存在的資料來源;
ODBC_REMOVE_DSN:刪除已經存在的資料來源;
ODBC_ADD_SYS_DSN:建立系統資料來源;
ODBC_CONFIG_SYS_DSN:配置或者修改已經存在的系統資料來源;
ODBC_REMOVE_SYS_DSN:刪除已經存在的系統資料來源;
ODBC_REMOVE_DEFAULT_DSN:刪除預設的資料來源。
參數lpszDriver用於指定ODBC資料來源的驅動程式類別,例如,為了指定Access資料來源,該參數應賦以字串"Microsoft Access Driver (*.mdb)\0"。 參數lpszAttributes用於指定ODBC資料來源屬性,例如:
"DSN=MYDB\0DBQ=D:\\Database\\Friends.mdb\0DEFAULTDIR=D:\\DATABASE\0\0"
該字串指定資料來源名稱(DSN)為MYDB,資料庫檔案(DBQ)為D:\Database\Friends.mdb,預設資料庫檔案路徑(DEFAULTDIR)為D:\DATABASE。
通過調用如下代碼可以通過應用程式動態建立資料來源MYDB:
BOOL CreateDSN()
{
char* szDesc;
int mlen;
szDesc=new char[256];
sprintf(szDesc,"DSN=%s: DESCRIPTION=TOC support source: \
DBQ=%s: FIL=MicrosoftAccess: \
DEFAULTDIR=D:\\Database:: ","TestDB","D:\\Friends.mdb");
mlen = strlen(szDesc);
for (int i=0; i<mlen; i++){
if (szDesc[i] == ':') szDesc[i] = '\0';
}
if (FALSE == SQLConfigDataSource(NULL,ODBC_ADD_DSN,
"Microsoft Access Driver (*.mdb)\0",
(LPCSTR)szDesc))
return FALSE; // 建立資料來源失敗。
else
return TRUE; // 建立資料來源成功。
}
5.2.2 步驟2:分配語句控制代碼
通常將ODBC中的語句看作SQL語句。前面已經提到,ODBC同資料庫的SQL介面通訊,驅動程式將ODBC的SQL映射到驅動程式的SQL。但是ODBC的SQL還攜帶了一些屬性資訊,用於定義資料來源串連的上下文,有些語句要求特殊的參數以便能夠執行,因此,每個語句都有一個指向定義語句所有屬性結構的控制代碼。
語句控制代碼的分配同環境控制代碼的分配相似,通過函數SQLAllocStmt實現,該函數的調用文法如下:
HSTMT hstmt;
RETCODE rcode;
m_retcode = :: SQLAllocStmt(hdbc, &hstmt );
if(rcode == SQL_SUCCESS) // 串連控制代碼建立成功
{
// 執行其它操作
…………
}
5.2.3 步驟3:準備並執行SQL語句
對於不同的應用程式需求,要準備的SQL語句也一定不一樣。通常的SQL語句包括SELECT、INSERT、UPDATA、DELETE、DROP等。
準備和執行一個SQL語句的方法有兩種,第一種是使用SQLExecDirect函數,可以一次執行一個SQL語句。很多請求都可以使用這個方法。調用SQLExecDirect函數的文法如下:
LPCSTR pszSQL;
strcpy(pszSQL, "SELECT * FROM EMPLOYEES");
retcode = ::SQLExecDirect(hstmt, (UCHAR*)pszSQL, SQL_NTS );
if(rcode == SQL_SUCCESS) // SQL語句執行成功
{
// 執行其它操作
…………
}
但是有些請求需要多次執行同一條語句,為此,ODBC提供了SQLPrepare函數和SQLExecute函數。調用的時候,只需要調用一次SQLPrepare函數,然後調用若干次SQLExecute函數。實際上,函數SQLExecDirect將SQLPrepare和SQLExecute的功能集中到了一起,多次調用SQLExecDirect顯然比調用一次SQLPrepare再調用若干次SQLExecute效率高。調用SQLPrepare和SQLExecute函數的文法如下:
LPCSTR pszSQL;
strcpy(pszSQL, "SELECT * FROM EMPLOYEES");
m_retcode = ::SQLPrepare( hstmt, (UCHAR*)pszSQL, SQL_NTS );
if(rcode == SQL_SUCCESS) // SQL語句準備成功
{
// 執行其它操作
…………
}
retcode = :: SQLExecute (hstmt, (UCHAR*)pszSQL, SQL_NTS );
if(rcode == SQL_SUCCESS) // SQL語句執行成功
{
// 執行其它操作
…………
}
5.2.4 步驟4:擷取結果集
SQL語句執行成功以後,應用程式必須準備接收資料,應用程式需要把SQL語句執行結果綁定到一個本機快取變數裡。但是SQL執行語句執行的結果並不是直接傳送給應用程式,而是在應用程式準備接收資料的時候通知驅動程式其已經準備好接收資料,應用程式通過調用SQLFetch函數返回結果集的一行資料。
由於返回的資料是存放在列中的,因此應用程式必須調用SQLBindCol函數綁定這些列。通常接收結果集時需要依次進行以下操作:
" 返回列的個數,執行SQLNumResultCols函數。
" 給出列中資料的有關資訊,例如列的名稱、資料類型和精度等,執行SQLDescribeCol函數。
" 把資料行繫結到應用程式的變數裡,執行SQLBindCol函數。
" 擷取資料,執行SQLFetch函數。
" 擷取長資料,執行SQLGetData函數。
應用程式首先調用SQLNumResultCols函數,獲知每個記錄裡有多少列,調用SQLDescribeCol函數取得每列的屬性,然後調用SQLBindCol函數將列資料繫結到指定的變數裡,最後調用SQLFetch函數或者SQLGetData函數擷取資料。
對於其它的SQL語句,應用程式重複這個過程。這個過程代碼如下:
retcode = ::SQLNumResultCols( m_hstmt, &wColumnCount );
if( m_retcode != SQL_SUCCESS ) // 列舉結果集列的個數不成功
{
// 釋放操作
…………
return;
}
LPSTR pszName;
UWORD URealLength;
SWORD wColumnCount;
UWORD wColumnIndex = 0;
SWORD wColumnType;
UDWORD dwPrecision;
SWORD wScale;
SWORD wNullable;
m_retcode = ::SQLDescribeCol( m_hstmt,
wColumnIndex, // 列的索引
pszName, // 列的名稱
256, // 存放列名稱的緩衝區大小
& nRealLength, // 實際得到列名稱的長度
&wColumnType, // 列的資料類型
&dwPrecision, // 精度
&wScale, // 小數點位元
&wNullable ); // 是否允許空值
if(retcode != SQL_SUCCESS ) // 執行不成功
{
// 釋放操作
…………
return;
}
retcode = ::SQLBindCol( m_hstmt,
uCounter, // 列索引
wColumnType, // 列資料類型
FieldValue, // 綁定的變數
dwBufferSize, // 變數記憶體大小
&BytesInBuffer); // 存放將來返回資料的大小的變數
if(retcode != SQL_SUCCESS ) // 執行不成功
{
// 釋放操作
…………
return;
}
::SQLFetch( m_hstmt );
// 此後可以從綁定的變數裡讀取列的值。
…………
5.2.5 步驟5:提交事務
當所有的SQL語句都被執行並接收了所有的資料以後,應用程式需要調用SQLEndTran提交或者回退事務。如果提交方式為手工(應用程式設定)方式,則需要應用程式執行這個語句以提交或者回退事務,如果是自動方式,當SQL語句執行後,該命令自動執行。
事務是為了維護資料的一致性和完整性而設計的概念,事務要求:要麼提交,將事務裡包含的更新操作都提交到資料庫裡;要麼會退,資料庫恢複到事務前的狀態,不會影響資料庫的一致性和完整性。通常情況下,檢索類SQL語句不涉及資料的更新,不會對資料的一致性和完整性產生影響,因此通常將檢索類SQL語句設定提交方式為自動,而將更新類SQL語句的提交方式設定為手工方式,便於通過代碼在交易處理中執行事務的提交或者會退,以維護資料庫的一致性和完整性。在大型的商業應用中,這個設定非常有用。
調用SQLEndTran函數的文法如下:
:: SQLEndTran(SQL_HANDLE_DBC , hdbc, SQL_COMMIT); // 提交事務
:: SQLEndTran(SQL_HANDLE_DBC , hdbc, SQL_ROLLBACK); // 會退事務
5.2.6 步驟6:斷開資料來源串連並釋放環境控制代碼
當應用程式使用完ODBC以後,需要使用SQLFreeHandle函數釋放所有語句控制代碼、串連控制代碼、環境控制代碼。這裡需要注意操作的順序,應該是先釋放所有語句控制代碼,調用SQLDisconnect函數解除與資料來源的串連,然後釋放所有串連控制代碼,最後釋放環境控制代碼,使應用程式同ODBC管理器的串連徹底解除。
5.3 ODBC API編程執行個體
5.3.1 執行個體概述
需求調查與分析
某小規模圖書公司為了對其出售圖書進行日常管理,需要開發一個小型的資料庫應用軟體,通過使用這個軟體,圖書公司可以實現對其出售圖書資訊的登記、瀏覽、出售、刪除和更新操作。
資料庫系統及其訪問技術
由於該公司營業規模較小,因此我們為這個應用選擇了Microsoft Access資料庫。在本節實現的資料庫應用執行個體中,我們使用物件導向編程方法,通過對ODBC API進行封裝,建立了一個可獨立編譯的資料庫操縱軟體包。該軟體包實現了CODBCDatabase類和CODBCRecordset兩個基本類,這兩個基本類使用ODBC API,建立了進行資料庫訪問編程的各種操作方法,通過使用這兩個類,資料庫開發人員不僅可以詳細瞭解ODBC API資料庫編程的方法,更可以在這兩個類的基礎上,很方便地實現資料庫應用軟體的開發。
執行個體實現效果
ODBCDemo1是本書用於闡述ODBC API資料庫編程的執行個體應用程式,該應用程式實現了某圖書公司對出售圖書的管理,包括資訊登記、瀏覽、出售、刪除和更新操作。應用程式運行介面5-1所示。
圖5-1 ODBCDemo1執行個體應用程式的運行介面
5.3.2 執行個體實現過程
資料庫設計
我們利用Microsoft Access工具設計本執行個體的資料庫結構。在本執行個體裡,我們需要利用資料庫存放圖書的如下資訊:
" 圖書基本資料:包括圖書的書號(ISBN)、書名、出版商、出版日期、作者、價格。
" 圖書類別資訊:我們把圖書分成了科學技術、語言文學、政治經濟、曆史地理、意識形態和藝術等類別,每個類別有不同的類別代碼。
" 圖書出售資訊:包括圖書出售日期、出售價格、出售數量。
為此我們為資料庫設計了四個表,表"基本資料"存放圖書的基本資料,表"出售資訊"存放圖書的出售資訊,表"類別資訊"存放圖書的類別資訊。為了便於資料訪問,我們還定義了一個視圖"出售圖書",該視圖管理了已經出售圖書的全部資訊。
表5-1列出了表"基本資料"的結構,表5-2列出了表"出售資訊"的結構,表5-3列出了表"類別資訊"的結構。
表5-1 表"基本資料"的結構
欄位名稱 類型 欄位名稱 類型
圖書ID(key) 自動編號 價格 貨幣
標題 文本 作者 文本
著作權日期 數字 類別ID 數字
ISBN 文本 出版商 文本
表5-2 表"出售資訊"的結構
欄位名稱 類型 欄位名稱 類型
圖書ID 自動編號 ISBN 文本
出售價格 數字 出售數量 數字
表5-3 表"類別資訊"的結構
欄位名稱 類型 欄位名稱 類型
類別ID(key) 自動編號 類別名稱 文本
在執行個體光碟片的Database目錄下,book.mdb檔案是存放圖書資訊的Access資料庫檔案,讀者可以查看這個檔案,瞭解這個資料庫的詳細資料。
建立ODBCDemo1工程
ODBCDemo1工程是一個基於對話方塊的應用程式,建立應用程式工程時需要選擇基於對話方塊的應用程式類型。
建立ODBCDemo1工程的操作步驟如下:
(1) 開啟VC++的工程建立嚮導。從VC++的菜單中執行"File>New"命令,將VC++ 6.0工程建立嚮導顯示出來。如果當前的選項標籤不是Project,單擊Project選項標籤將它選中。在左邊的列表裡選擇MFC AppWizard(exe)項,在Project Name編輯區裡輸入工程名稱"ODBCDemo1",並在Location編輯區裡調整工程路徑,5-2所示。
(2) 選擇應用程式的架構類型。在圖5-2中,單擊"工程建立嚮導"視窗的OK按鈕,開始建立ODBCDemo1工程。建立ODBCDemo1工程的第一步是選擇應用程式的架構類型。5-3所示。彈出的"MFC AppWizard - Step 1"對話方塊裡,選擇"Dialog Based",保持資源的語言類型為"中文",單擊"Next >"按鈕,執行第二步。
圖5-2 工程建立嚮導
圖5-3 選擇應用程式的架構類型
(3) 設定應用程式支援的特性。在彈出的"MFC AppWizard - Step 2 of 4"對話方塊裡,設定如下三項:
" About box
" 3D Controls
" ActiveX Controls
在編輯區裡輸入"ODBC API編程執行個體 - 圖書管理",作為應用程式對話方塊的標題,5-4所示。
圖5-4 設定應用程式支援的特性
(4) 選擇工程風格和MFC類庫的載入方式。在"MFC AppWizard - Step 2 of 4"對話方塊裡,單擊"Next >"按鈕,彈出"MFC AppWizard - Step 3 of 4"對話方塊。在對話方塊裡設定如下三項:
" MFC Standard
" Yes, Please
" As a Shared DLL
5-5所示,單擊"Next >"按鈕,進入下一步。
圖5-5 選擇工程風格和MFC類庫的載入方式
(5) 顯示工程建立資訊。在本例中,ODBCDemo1工程包含了兩個類:
" CODBCDemo1App類,工程的應用類。
" CODBCDemo1Dlg類,工程主對話方塊類。
這兩個類構成了應用程式工程的主要架構。在"MFC AppWizard - Step 3 of 4"對話方塊裡單擊Finish按鈕,工程建立嚮導將該次工程建立的資訊顯示在一個對話方塊裡,5-6所示。在對話方塊裡單擊"OK"按鈕,ODBCDemo1工程建立完成。
圖5-6 工程建立資訊
建立資料來源
為了示範應用程式對資料來源的操作,該執行個體提供了一個名稱為"Authors.mdb"的Access資料庫,應用程式的所有操作將在該資料庫上實施。通過第1章對ODBC的介紹我們知道,要通過ODBC API實現對資料庫的操作,必須為資料庫連接建立資料來源。下面詳細敘述ODBC資料來源的建立過程。
操作步驟:
(1) 開啟ODBC資料來源管理器。如果使用的是Windows 98作業系統,需要在控制台裡雙擊"資料來源 (ODBC)"表徵圖,開啟ODBC資料來源管理器;如果使用的是Windows 2000(家族)作業系統,需要在控制台裡雙擊"管理工具"表徵圖,然後在管理工具裡雙擊"資料來源 (ODBC)"表徵圖,開啟ODBC資料來源管理器,5-7所示。
圖5-7 ODBC 資料來源管理器
(2) 建立ODBCDemo1資料來源。在資料來源管理器裡單擊"添加"按鈕,彈出"建立新資料來源"對話方塊,開始建立ODBCDemo1資料來源,5-8所示。首先選擇資料來源驅動程式,在列表裡,選擇"Microsoft Access Driver(*.mdb)"項。
圖5-8 為要建立新的資料來源選擇驅動程式
(3) 配置建立的新資料來源。在"建立新資料來源"對話方塊裡單擊"完成"按鈕,彈出"ODBC Microsoft Access安裝"對話方塊,在對話方塊裡配置建立的新資料來源。5-9所示,輸入資料來源名稱"ODBCDemo1",在說明編輯區裡輸入"Data source for ODBC API programming.",單擊"選擇"按鈕,選擇要關聯的Microsoft Access資料庫(*.mdb),在本例裡,我們選擇Databases目錄下的book.mdb檔案,保持其它設定。
圖5-9 "ODBC Microsoft Access安裝"對話方塊
(4) 確認並建立資料來源。在"ODBC Microsoft Access安裝"對話方塊裡單擊"確定"按鈕,完成ODBCDemo1資料來源的建立,並返回ODBC資料來源管理器,資料來源管理器顯示了剛才建立的ODBCDemo1資料來源,5-10所示。
(5) 單擊"確定"按鈕,完成資料來源建立。
設計應用程式介面
在上一節,我們利用工程建立嚮導建立了一個基於對話方塊的工程,該工程自動將IDD_ODBCDEMO1_DIALOG對話方塊添加到工程資源裡,我們需要對該對話方塊進行重新設計,以實現應用程式的操作入口。
圖5-10 建立了ODBCDemo1資料來源的ODBC資料來源管理器
設計IDD_ODBCDEMO1_DIALOG對話方塊的操作步驟如下:
(1) 開啟IDD_ODBCDEMO1_DIALOG對話方塊。如果VC++的工程工作區視窗沒有顯示,應該先開啟該視窗,執行"View>Workspace"功能表命令,或者按下【Alt】+0快速鍵,將VC++的工程工作區視窗顯示出來。在工程工作區視窗底部單擊"Resource"選項標籤,顯示"工程資源"選項標籤,5-11所示。在視窗裡雙擊IDD_ODBCDEMO1_DIALOG項,開啟IDD_ODBCDEMO1_DIALOG對話方塊,5-12所示。
圖5-11 "工程資源"選項標籤
圖5-12 初始的IDD_ODBCDEMO1_DIALOG對話方塊
(2) 設計IDD_ODBCDEMO1_DIALOG對話方塊。在IDD_ODBCDEMO1_DIALOG對話方塊上添加表5-4所列出的資源。
表5-4 IDD_ODBCDEMO1_DIALOG對話方塊的資源
資源類型 資源ID 標題 功能
編輯框 IDC_TITLE 顯示書的標題資訊
編輯框 IDC_AUTHOR 顯示書的作者資訊
編輯框 IDC_ISBN 顯示書號
編輯框 IDC_PUBLISHER 顯示書的出版商資訊
編輯框 IDC_PRICE 顯示書的單價
編輯框 IDC_YEARPUBLISH 顯示書的出版日期
組合框 IDC_STATIC 資訊: 資訊容器的標題
組合框 IDC_STATIC 操作 操作容器標題
按鈕 IDC_NEXT >> 下一條記錄
按鈕 IDC_PREVIOUS << 上一條記錄
按鈕 IDC_END >| 最後一條記錄
按鈕 IDC_HOME |< 第一條記錄
按鈕 IDC_INSERT 插入 插入記錄操作按鈕
按鈕 IDC_DELETE 刪除 刪除記錄操作按鈕
按鈕 IDC_UPDATE 更新 更新記錄操作按鈕
按鈕 IDC_CONNECT 啟動串連 啟動串連操作按鈕
按鈕 IDC_DISCONNECT 中斷連線 中斷連線操作按鈕
按鈕 IDCANCEL 退出 退出程式操作按鈕
標籤 IDC_STATIC 書名: 書名標籤
標籤 IDC_STATIC 作者: 作者標籤
標籤 IDC_STATIC 書號: 書號標籤
標籤 IDC_STATIC 出版時間: 出版時間標籤
標籤 IDC_STATIC 出版商: 出版商標籤
標籤 IDC_STATIC 單價: 單價標籤
標籤 IDC_STATIC Info 資訊標籤
(3) 儲存資源。按下【Ctrl】+【S】快速鍵,儲存資源修改,完成後IDD_ODBCDEMO1_DIALOG對話方塊5-13所示。
編寫工程代碼
為了便於介紹ODBC API編程的方法和技巧,ODBCDemo1工程特別以物件導向的方式建立了一個CODBCDatabase類和一個CODBCRecordSet類。
我們首先介紹CODBCDatabase類和CODBCRecordSet類的功能與實現方法,然後介紹使用這兩個類建立一個基於ODBC API的資料庫應用程式。由於CODBCDatabase類和CODBCRecordSet類用到了一些全域常量、宏以及結構,我們首先建立這些內容。
圖5-13 設計完成後的IDD_ODBCDEMO1_DIALOG對話方塊
1. 建立全域常量、宏以及資料結構
DECLARATIONS_FOR_ODBC.h檔案包含了所有的全域常量、宏以及資料結構。建立全域變數、宏以及資料結構的操作步驟如下:
(1) 在DECLARATIONS_FOR_ODBC.h檔案裡引入下面的標頭檔:
#include "sqltypes.h"
#include "sql.h"
#include "sqlext.h"
上述的標頭檔包含了所有SQL資料類型、函數、擴充函數的聲明。
(2) 添加下面的全域變數:
// Constants ===========================
#define MAX_FIELDS 20 // 表示最大的欄位數的常量
// 應用程式運行狀態常量
#define ODBC_DISPLAY_ERRORINFO_IN_DEBUG 0x00000001
#define ODBC_DISPLAY_ERRORINFO_IN_RELEASE 0x00000002
#define ODBC_DISPLAY_ERRORINFO_IN_FILE 0x00000004
#define ODBC_EXCEPTION_SET_STMT_OPTION 100
// 表示ODBC異常發生時的移動操作的常量
#define ODBC_EXCEPTION_MOVE_ABSOLUTE 10
#define ODBC_EXCEPTION_MOVE_RELATIVE 20
#define ODBC_EXCEPTION_MOVE_BOOKMARK 30
#define ODBC_EXCEPTION_MOVE_NEXT 40
#define ODBC_EXCEPTION_MOVE_PREVIOUS 50
#define ODBC_EXCEPTION_MOVE_LAST 60
#define ODBC_EXCEPTION_MOVE_FIRST 70
// ODBC狀態常量
#define ODBC_HSTMT_ALLOCATED 0x00000001
#define ODBC_BUFFERS_ALLOCATED 0x00000002
#define ODBC_BOOKMARKS 0x00000004
#define ODBC_VALID_CURSOR 0x00000008
#define ODBC_CURSOR_OPEN 0x00000010
#define ODBC_NO_RECORDS 0x00000020
// 初始化方式
#define ODBC_INITIALIZE_COLUMN_NAME 0x00000001
#define ODBC_INITIALIZE_BIND 0x00000002
// SQL語句執行方式
#define ODBC_EXEC_DIRECT 1
#define ODBC_EXEC_PREPARE 2
#define ODBC_EXEC_PREPARE_AND_EXECUTE 3
(3) 添加資料結構,包括:
" ODBC_OPTION:用於設定串連或者語句選項
" ODBC_BIND_PARAMETER:用於資料行繫結的資料結構
" ODBC_DATA_AT_EXECUTION:用於運行時填充列的資料結構
" ODBC_GET_DATA:用於讀資料的資料結構
這些資料結構的代碼如下:
// Structures ========================
typedef struct tagODBC_OPTION
{
UWORD m_wOption;
UDWORD m_dwParam;
} ODBC_OPTION, * PODBC_OPTION;
typedef struct tagODBC_BIND_PARAMETER
{
HSTMT m_hstmt; // 串連控制代碼
UWORD m_wIndex; // 綁定參數的索引
SWORD m_wParamType; // 參數類型
SWORD m_wCType; // C型別參數
SWORD m_wSQLType; // SQL資料類型
UDWORD m_dwPrecision; // 精度
SWORD m_wScale; // 小數點位元
PTR m_pBuffer; // 存放資料的緩衝
SDWORD m_dwMaxBytes; // 緩衝容量
SDWORD* m_pdwBytesRetrieved; // 實際資料大小
} ODBC_BIND_PARAMETER, * PODBC_BIND_PARAMETER;
typedef struct tagODBC_DATA_AT_EXECUTION
{
short m_sParameterIndex; // 資料轉送參數的索引
UINT m_uBufferLength; // 緩衝大小
void* m_pBuffer; // 緩衝指標
} ODBC_DATA_AT_EXECUTION, * PODBC_DATA_AT_EXECUTION;
typedef struct tagODBC_GET_DATA
{
UWORD wColumn; // 資料擷取的列索引
SWORD wCType; // C類型
PTR pBuffer; // 緩衝指標
SDWORD dwMaxBytes; // 緩衝容量
SDWORD dwByteRead; // 實際資料大小
} ODBC_GET_DATA, * PODBC_GET_DATA;
(4) 添加宏。定義宏的目的是便於編程時對代碼的靈活使用。本執行個體定義了錯誤檢查、參數綁定、資料複製和擷取操作的宏。這些宏的原始碼如下:
// Macros ==============================
#ifdef _DEBUG // 在DEBUG狀態下將字串作為異常發出
#define CHECK(text) \
if( CheckODBCError() == FALSE ) throw text;
#else // 而在其他狀態時發出異常為空白字串
#define CHECK(text) \
if( CheckODBCError() == FALSE ) throw (char*)NULL;
#endif
#define CHECK_FOR_ERROR() CheckODBCError();
#define CHECK_AND_BREAK() \
if( CheckODBCError() == FALSE ) return FALSE;
#define CHECK_AND_THROW() \
if( CheckODBCError() == FALSE ) throw 0;
// 執行綁定參數操作
#define ODBC_BIND_PARAMETER( par, hstmt, index, type, c_type, sql_type,
prec, buff, max, read ) \
par.m_hStmt = hstmt;
par.m_wIndex = index;
par.m_wParamType = type;
par.m_wCType = c_type;
par.m_wSQLType = sql_type;
par.m_dwPrecision = prec;
par.m_wScale = 0;
par.m_pBuffer = buff;
par.m_dwMaxBytes = max;
par.m_pdwBytesRetrieved = read;
// 執行綁定二進位參數操作
#define ODBC_BIND_PARAMETER_BINARY( par, index, type, byte_count,
buffer, read ) \
par.m_hstmt = SQL_NULL_HSTMT;
par.m_wIndex = index;
par.m_wParamType = type;
par.m_wCType = SQL_C_BINARY;
par.m_wSQLType = SQL_LONGVARBINARY;
par.m_dwPrecision = byte_count;
par.m_wScale = 0;
par.m_pBuffer = buffer;
par.m_dwMaxBytes = byte_count + 1;
par.m_pdwBytesRetrieved = read;
// 執行資料賦值操作
#define ODBC_DATA_AT_EXECUTION( data, index, length, buffer ) \
data.m_sParameterIndex = index;
data.m_uBufferLength = length;
data.m_pBuffer = buffer;
// 執行資料擷取操作
#define ODBC_GET_DATA( data, column, ctype, buffer, max ) \
data.wColumn = column;
data.wCType = ctype;
data. pBuffer = buffer;
data.dwMaxBytes = max;