大多數應用程序都使用數據庫,各種管理軟件、ERP、CRM系統均需要數據庫來保存和維護應用程序的數據,在VC中提供了多種數據庫訪問技術,不過目前最流行的是ODBC(開放式數據庫接口)和ADO(活動對象模型)。
一.數據庫技術初步
1.ODBC基本概念
ODBC(Open Database Connectivity,開放數據庫互連)是微軟公司開放服務結構(WOSA,Windows Open Services Architecture)中有關數據庫的一個組成部分,它建立了一組規范,并提供了一組對數據庫訪問的標準API(應用程序編程接口)。這些API利用SQL來完成其大部分任務。ODBC本身也提供了對SQL語言的支持,用戶可以直接將SQL語句送給ODBC。
一個基于ODBC的應用程序對數據庫的操作不依賴任何DBMS,不直接與DBMS打交道,所有的數據庫操作由對應的DBMS的ODBC驅動程序完成。也就是說,不論是FoxPro、Access還是Oracle數據庫,均可用ODBC API進行訪問。由此可見,ODBC的最大優點是能以統一的方式處理所有的數據庫。
一個完整的ODBC由下列幾個部件組成:
應用程序(Application)。
ODBC管理器(Administrator)。該程序位于Windows 95控制面板(Control Panel)的32位ODBC內,其主要任務是管理安裝的ODBC驅動程序和管理數據源。
驅動程序管理器(Driver Manager)。驅動程序管理器包含在ODBC32.DLL中,對用戶是透明的。其任務是管理ODBC驅動程序,是ODBC中最重要的部件。
ODBC API。
ODBC 驅動程序。是一些DLL,提供了ODBC和數據庫之間的接口。
數據源。數據源包含了數據庫位置和數據庫類型等信息,實際上是一種數據連接的抽象。
應用程序要訪問一個數據庫,首先必須用ODBC管理器注冊一個數據源,管理器根據數據源提供的數據庫位置、數據庫類型及ODBC驅動程序等信息,建立起ODBC與具體數據庫的聯系。這樣,只要應用程序將數據源名提供給ODBC,ODBC就能建立起與相應數據庫的連接。
在ODBC中,ODBC API不能直接訪問數據庫,必須通過驅動程序管理器與數據庫交換信息。驅動程序管理器負責將應用程序對ODBC API的調用傳遞給正確的驅動程序,而驅動程序在執行完相應的操作后,將結果通過驅動程序管理器返回給應用程序。
在訪問ODBC數據源時需要ODBC驅動程序的支持。用Visual C++ 5.0安裝程序可以安裝SQL Server、 Access、 Paradox、 dBase、 FoxPro、 Excel、 Oracle 和Microsoft Text等驅動程序.在缺省情況下,VC5.0只會安裝SQL Server、 Access、 FoxPro和dBase的驅動程序.如果用戶需要安裝別的驅動程序,則需要重新運行VC 5.0的安裝程序并選擇所需的驅動程序。
2.ADO對象訪問模型
1)ADO是微軟整個COM戰略體系中的一個組成部分
活動數據對象(ADO)是一組由微軟提供的COM組件。 ADO建立在微軟所提倡的COM體系結構之上,它的所有接口都是自動化接口,因此在C++、VisualBasic、Delphi等支持COM的開發語言中通過接口都可以訪問到ADO。ADO通過使用OLE DB這一新技術實現了以相同方式可以訪問關系數據庫、文本文件、非關系數據庫、索引服務器和活躍目錄服務等的數據,擴大了應用程序中可使用的數據源范圍,從而成為微軟整個COM戰略體系中訪問數據源組件的首選,是ODBC的替代產品。
2)ADO對象模型組成
與微軟的其它數據訪問模型DAO和RDO相比,ADO對象模型非常精煉,僅由三個主要對象Connection、Command、Recordset和幾個輔助對象組成。Connection對象提供OLE DB數據源和對話對象之間的關聯,它通過用戶名稱和口令來處理用戶身份的鑒別,并提供事務處理的支持;它還提供執行方法,從而簡化數據源的連接和數據檢索的進程。Command對象封裝了數據源可以解釋的命令,該命令可以是SQL命令、存儲過程或底層數據源可以理解的任何內容。Record set用于表示從數據源中返回的表格數據,它封裝了記錄集合的導航、記錄更新、記錄刪除和新記錄的添加等方法,還提供了批量更新記錄的能力。其它輔助對象則分別提供封裝ADO錯誤、封裝命令參數和封裝記錄集合的列。
3)ADO的特點分析
(a)由于封裝了許多底層工作,使用ADO與使用ODBC幾乎是一樣方便。
(b) ADO不僅具有ODBC的主要功能,而且ADO適用的數據源的范圍要大的多。
(c)在定義ADO記錄集變量和數據庫表字段綁定類時,要求記錄集的字段變量、狀態變量與數據庫表字段的個數、順序必須相同。這一點比在FMC中使用ODBC要復雜一些。但在數據庫字段與ADO記錄集字段變量綁定的宏中,ADO 提供的數據類型要遠多于FMC中的RFX(如日期時間類型,在ODBC中只能轉換為Cstring類型)。
(d)ADO允許同一Connection實例下有多個Record set實例。
(e)ADO允許進行批更新(使用的Update Batch方法),這樣將大大減輕網絡負擔,提高數據庫處理效率。 4) ADO在Visual C++中的使用
利用微軟在Micrsoft Studio 6中提供的ADO2,可以在Visual C++中使用ADO接口操縱SQL SERVER數據庫。在編譯型高級語言中使用ADO,比起在一些腳本語言(如Visual Basic Scropt和JavaScript)中使用ADO要困難一些。
以下給出一個Visual C++下使用ADO的Connection對象及其Record set對象的基本步驟:
(a) 使用import指令引入ADO2組件
例:#import "C:\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")
(b) 定義CADORecordBinding 的派生類,用于程序與數據庫表字段的交互,該類的定義可參見icrsint.h。
例:
class CIntlive : public CADORecordBinding
{
public:
DBTIMESTAMP m_datetime; //定義ADO記錄集字段變量(與數據庫表字段相對應)
long m_key;
long m_value;
long m_quality;
WORD m_stsdatetime; //定義ADO記錄集狀態變量
WORD m_stskey;
WORD m_stsvalue;
WORD m_stsquality;
BEGIN_ADO_BINDING(CIntlive) //將數據庫字段與ADO記錄集字段變量綁定
ADO_VARIABLE_LENGTH_ENTRY2(1,adDBTimeStamp,m_datetime,sizeof(m_datetime),m_stsdatetime,true)
ADO_NUMERIC_ENTRY(2,adInteger,m_key,10,0,m_stskey,true)
ADO_NUMERIC_ENTRY(3,adInteger,m_value,10,0,m_stsvalue,true)
ADO_NUMERIC_ENTRY(4,adInteger,m_quality,10,0,m_stsquality,true)
END_ADO_BINDING()
};
(c) 調用CoInitialize初始化COM ::CoInitialize(NULL);
(d) 聲明ADO的Connection對象指針和Recordset對象指針并初始化。(類型名在 msado15.dll中已定義)
例:
_ConnectionPtr pConnection1 = NULL;
_RecordsetPtr rstADO1 = NULL;
(e) 定義CADORecordBinding派生類的實例及其Bind接口指針。
例:
CIntlive m_intdata;
IADORecordBinding *rstADOBind1 = NULL;
(f) 產生Connection對象實例和Record set對象實例。
例:
pConnection1.CreateInstance(_uuidof(Connection));
rstADO1.CreateInstance(__uuidof(Recordset)) ;
(g) 連接到數據庫并打開Record set對象,其中open函數的參數的使用方法可參見微軟MSDN中ADO 相應對象參數的Basic描述。
例:
PConnection1->Open("driver={SQL server};server=servera;uid=sa;pwd=;database=pubs","","",NULL);
rstADO1->Open("data", _variant_t((IDispatch *)pConnection1,true),
adOpenKeyset,adLockBatchOptimistic, adCmdTable);
(h) 將CADORecordBinding派生類的實例聯編到Record set對象的Bind接口。
例:
RstADOBind1->BindToRecordset(&m_intdata);
(i) 對Record set對象實例進行操作。操作方法可參見微軟MSDN中ADO Record set對象相應方法的Basic描述。
例:
rstADO1->Move Next(); //移動游標到下一條記錄
rstADO1->Update(_variant_t("quality"),_variant_t("3"))); //修改記錄的quality字段的值為3
rstADO1->Update Batch(adAffectAll)); //將在Record set對象上的所有更新一次送入數據庫
(j) 關閉Record set對象并釋放Bind接口。
例:
RstADO1->Close();
RstADOBind2->Release();
(k) 關閉連接 pConnection1->Close();
(l) 調用CoUnitialize釋放COM資源 ::CoUninitialize();
5) 結論
作為ODBC的替代產品,ADO確實有其過人之處。由于ADO數據源幾乎覆蓋了目前常見的數據源類型,對于ODBC所不支持的數據源,ADO無疑是唯一的選擇。而ADO的批更新功能,更是網絡環境下大數據量更新應用的重要因素。由于ADO缺乏大量的第三方廠商的支持,使得ADO目前遠不如ODBC普及,但其面向對象的特性將使ADO具有比較廣闊的發展前景。
3.ADO與ODBC的區別
有很多種使用數據庫的方法,對大多數數據庫來說,選擇C++這種產品也許并不適宜。我們知道,像dBASE IV,FoxPro,Oracle和Access這樣的產品是完全以數據庫管理為中心的。事實上,這些產品非常善于創建數據庫管理器,以至于它們確實并不善于做太多其它的工作。即使要用更通用化而非更專用化的數據庫產品來執行一些類型的工作,在使程序設計更容易這一方面,像VisualBasic和Delphi這樣的RAD環境也要比Visual C++強很多。
你是不是對我的說法感到很奇怪?下面我就要談一談,在談到使用數據庫管理系統(DBMS)這個話題時,用Visual C++實際上可以做些什么。雖然上述其它語言使得編寫成熟的包括用戶界面和高速搜索能力的DBMS就像孩子做游戲一樣容易,但是,它們缺少Visual C++可以提供的某些重要東西。你不能為使用Access的數據庫輕松地編寫出實用程序。正像實用程序的定義所說的,實用程序應該很小并且具備可移植性——Access應用程序卻不是這樣。即使用Access這樣的產品創建的程序可以很小并且可以移植,你仍有其它方面的需求:底層的功能。
注:編寫數據庫實用程序及驅動程序時,可以選擇Visual C++語言。
想像一下,使用像Visual Basic這樣的語言來與實時數據采集設備打交道的情況。在進行底層訪問時,RAD的保護環境常常使程序員不能進行有效的處理。當然,數據采集設備幾乎不依賴于簡明的連接。你打算如何把Visual Basic和外部的數據源連接起來呢?數據源甚至可能不了解Windows,DOS或類似的成熟的操作系統。
只要使用得當,很容易看到Visual C++是一種不可或缺的數據庫管理工具。針對大規模的應用程序,即使你仍想依賴于Visual Basic這樣的RAD語言,也請考慮一下Visual C++,它創建的程序規模小、提供底層訪問并能提供實時訪問。事實上,你可能還沒有想到,Visual C++數據庫應用程序的市場是很有潛力的。隨著人們在旅途中越來越多地使用膝上型和掌上型電腦,這兩類電腦上的數據庫應用程序也變得越來越普通。你也許能夠適應今天的膝上型電腦上的Access應用程序,但談到硬盤大小或內存需求時,公司里較老的膝上型電腦可能就達不到要求。運行Windows CE的掌上型電腦在運行這個Access應用程序時,肯定會發生故障。在這一數據庫市場的新領域,Visual C++提供了無價無限的工具。
Web鏈接 談到使用Visual C++和數據庫,其實你并不孤單。從一開始就有數據庫專用新聞組提供有關數據庫創建技巧的幫助,比如microsoft.public.access。不過,這些新聞組提供的是通用信息,對實際編寫應用程序并非全都那么有用。專門針對Visual C++問題的新聞組是microsoft.public.vc.database和microsoft.public.vc.mfcdatabase。如果你決定用ODBC訪問數據庫,可能還要查看一下microsoft.public.odbc.sdk新聞組,它討論的不僅僅是SDK。對最新技術感興趣的程序員可以查閱microsoft.public.ado新聞組,或者microsoft.public.oledb(對象鏈接和嵌入數據庫)新聞組,前者討論 ADO,后者討論ADO的基礎技術。在microsoft.public.ado.rds有一個ADO子組,它討論遠程數據訪問。
既然所有的疑惑都消除了,大多數人的信心也就增強了,下面我們就介紹兩種使C++訪問數據庫中的數據的主要方法:ODBC(開放數據庫互連)和ADO(ActiveX數據對象)。在本章中,將介紹這兩種類型的訪問方法,但我想你會發現,ADO方法是針對新的程序設計情形而采用的。它克服了早期技術的諸多限制,依賴于Microsoft新的底層訪問方法OLE-DB(對象鏈接和嵌入數據庫)。在本書的后面我們會看到,用ADO和Visual C++提供的各種向導來匯集數據庫工程,其速度有多快。
注 ODBC通常用來訪問不具備OLE-DB特性的非Microsoft數據庫中的數據;16位的ODBC驅動程序工作起來可能非常緩慢。
ODBC素以最慢的數據訪問方法而著稱,但是很可惜,當ADO或DAO都不支持某個數據庫管理器而ODBC支持這個數據庫管理器時,在這種特定的情形下,你仍然需要使用ODBC。在大多數情況下,這意味著要從數據庫廠商那里獲得所需的驅動程序,雖然Visual C++確實附帶了一些產品的驅動程序(如果你正在使用數據庫管理器的某些神秘功能,那么就需要建立自己的接口棗這并不是一件十分困難的事)。本質上講,你總是要使用ODBC來訪問Microsoft產品之外的其它DBMS產品所創建的數據庫,這些數據庫并不具備OLE-DB功能。ODBC還要求做一些額外的工作棗為ADO調整Visual C++中的大部分向導。
高級技巧
除了使用ADO和ODBC外,你還可以使用像DAO(數據訪問對象)這樣的早期技術,該技術包含在像Access這樣的Microsoft產品中。DAO依賴于用Microsoft Access自動獲得的Microsoft Jet數據庫引擎。DAO還是較早版的Visual Basic所使用的引擎(最新版的Visual Basic和Visual C++依賴于相同的ADO/OLE-DB組合),所以如果需要支持較早的Visual Basic應用程序,那么DAO仍是一個不錯的選擇。
盡管Microsoft文件聲明,可以用DAO訪問非Microsoft產品建立的數據庫,但你仍會發現,在這種情況下,使用ADO和ODBC要好得多。這樣的話,不但兼容性問題會少一些,速度也將有所提高,因為數據請求經過的接口層減少了。有一條經驗要記住,DAO是設計用來處理MDB文件的。
ADO的一個問題是,它不支持遠程通信。這是Microsoft提出RDO(遠程數據對象)的原因之一。這種特別技術在Visual Basic應用程序中的使用,要比在Visual C++中的使用多得多,所以我猜想,你們中有很多人都在使用它。但是,記住RDO仍是一種生命力很強的技術,這一點很重要。ADO確實具有替代RDO的遠程數據服務(RDS)特征。換言之,ADO在一個軟件包中提供了DAO和RDO兩種功能性。
4.MFC中相關類和ADO類庫簡介
1)單獨使用CRecordSet
一般情況下AppWizard會在數據庫應用程序中自動產生CRecordset的派生類,并將派生類和某個數據源中的表聯系起來也可以和視圖上的子窗口聯系起來。但是有時這樣做會影響到程序的靈活性,這時候我們可以單獨使用CRecordSet類。利用CRecordSet類我們可以執行SQL語句,并可以讀出結果集中數據。
首先我們需要包含頭文件afxdb.h,可以將#include 添加到stdafx.h文件中。此外在使用CRecordset時必須有一個又一個CDatabase對象,該對象的作用是管理數據源連接。然后可以產生一個CRecordset對象,利用BOOL CRecordset::Open( UINT nOpenType = AFX_DB_USE_DEFAULT_TYPE, LPCTSTR lpszSQL = NULL, DWORD dwOptions = none )可以執行SQL語句。
但執行成功后,可以調用以下的函數滾動光標,讀取數據。
MoveFirst 移動光標到第一條記錄處
MoveNext 移動光標到后一條記錄處
MovePrev 移動光標到前一條記錄處
MoveLast 移動光標到最后一條記錄處
IsBOF 檢測光標是否在第一條記錄上
IsEOF 檢測光標是否在最后一條記錄上
GetFieldValue 得到結果中數據
下面是具體代碼:
/*
假設CDatabase m_dbConn為成員變量
假設有一個表有如下SQL語句產生:CREATE TABLE table1(loc_id not null)
*/
void CYourClass::ConnectToDB()
{//連接數據庫
BOOL fOK = m_dbConn.Open("test");
TRACE("connect fOK=%d\n",m_dbConn);
}
void CYourClass::Select()
{//執行SELECT語句
CRecordset rec(&m_dbConn);
BOOL fOK = rec.Open(CRecordset::forwardOnly,"select loc_id from table1 order by loc_id");
TRACE("select fOK = %d\n",fOK);
TRACE("返回的列數為:%d\n",rec.GetRowsetSize());
CString szResult;
while(!rec.IsEOF())
{
rec.GetFieldValue((int)0,szResult);
rec.MoveNext();
TRACE("fetch : %s\n",szResult);
}
}
此外CRecordset::GetFieldValue有很多種原型,你可以通過指定列位置或是字段名來獲取數據:
void GetFieldValue( LPCTSTR lpszName, CDBVariant& varValue, short nFieldType = DEFAULT_FIELD_TYPE );
void GetFieldValue( short nIndex, CDBVariant& varValue, short nFieldType = DEFAULT_FIELD_TYPE );
void GetFieldValue( LPCTSTR lpszName, CString& strValue );
void GetFieldValue( short nIndex, CString& strValue );
如果使用CDBVariant類型變量來獲取結果,你可以得到任何類型的結果。在CDBVariant::m_dwType成員變量中記錄了該變量所包含的數據類型,根據該變量的值你可以確定數據類型并引用CDBVariant對象中的相應成員變量。
2)vc數據庫編程中CDatabase類的用法簡介
要建立與數據源的連接,首先應構造一個CDatabase對象,然后再調用CDatabase的Open成員函數.Open函數負責建立連接,其聲明為
virtual BOOL Open( LPCTSTR lpszDSN, BOOL bExclusive = FALSE, BOOL bReadOnly = FALSE, LPCTSTR lpszConnect = “ODBC;”, BOOL bUseCursorLib = TRUE ); throw( CDBException, CMemoryException );
參數lpszDSN指定了數據源名(構造數據源的方法將在后面介紹),在lpszConnect參數中也可包括數據源名,此時lpszDSN必需為NULL,若在函數中未提供數據源名且使lpszDSN為NULL,則會顯示一個數據源對話框,用戶可以在該對話框中選擇一個數據源.參數bExclusive說明是否獨占數據源,由于目前版本的類庫還不支持獨占方式,故該參數的值應該是FALSE,這說明數據源是被共享的.參數bReadOnly若為TRUE則對數據源的連接是只讀的.參數lpszConnect指定了一個連接字符串,連接字符串中可以包括數據源名、用戶帳號(ID)和口令等信息,字符串中的"ODBC"表示要連接到一個ODBC數據源上.參數bUseCursorLib若為TRUE,則會裝載光標庫,否則不裝載,快照需要光標庫,動態集不需要光標庫. 若連接成功,函數返回TRUE,若返回FALSE,則說明用戶在數據源對話框中按了Cancel按鈕。若函數內部出現錯誤,則框架會產生一個異常。
下面是一些調用Open函數的例子。
CDatabase m_db; //在文檔類中嵌入一個CDatabase對象
//連接到一個名為"Student Registration"的數據源
m_db.Open("Student Registration");
//在連接數據源的同時指定了用戶帳號和口令
m_db.Open(NULL,FALSE,FALSE,"ODBC;DSN=Student Registration;UID=ZYF;PWD=1234");
m_db.Open(NULL); //將彈出一個數據源對話框
要從一個數據源中脫離,可調用函數Close。在脫離后,可以再次調用Open函數來建立一個新的連接.調用IsOpen可判斷當前是否有一個連接,調用GetConnect可返回當前的連接字符串。函數的聲明為
virtual void Close( );
BOOL IsOpen( ) const; //返回TRUE則表明當前有一個連接
const CString& GetConnect( ) const;
CDatabase的析構函數會調用Close,所以只要刪除了CDatabase對象就可以與數據源脫離。
3)vc數據庫編程中CRecordView類簡介
CRecordView(記錄視圖)是CFormView的派生類,它提供了一個表單視圖來顯示當前記錄.用戶可以通過表單視圖顯示當前記錄.通過記錄視圖,可以修改、添加和刪除數據.用戶一般需要創建一個CRecordView的派生類并在其對應的對話框模板中加入控件.
記錄視圖使用DDX數據交換機制在表單中的控件和記錄集之間交換數據。在前面介紹的DDX都是在控件和控件父窗口的數據成員之間交換數據,而記錄視圖則是在控件和一個外部對象(CRecordset的派生類對象)之間交換數據.清單10.3顯示了一個CRecordView的派生類的DoDataExchange函數,讀者可以看出,該函數是與m_pSet指針指向的記錄集對象的域數據成員交換數據的,而且,交換數據的代碼是ClassWizard自動加入的.在后面的例子中,將向讀者介紹用ClassWizard連接記錄視圖與記錄集對象的方法.
用來與記錄集對象的域數據成員交換數據的DoDataExchange函數
void CSectionForm::DoDataExchange(CDataExchange* pDX)
{
CRecordView::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CSectionForm)
DDX_FieldText(pDX, IDC_COURSE, m_pSet->m_CourseID, m_pSet);
DDX_FieldText(pDX, IDC_SECTION, m_pSet->m_SectionNo, m_pSet);
DDX_FieldText(pDX, IDC_INSTRUCTOR, m_pSet->m_InstructorID, m_pSet);
DDX_FieldText(pDX, IDC_ROOM, m_pSet->m_RoomNo, m_pSet);
DDX_FieldText(pDX, IDC_SCHEDULE, m_pSet->m_Schedule, m_pSet);
DDX_FieldText(pDX, IDC_CAPACITY, m_pSet->m_Capacity, m_pSet);
//}}AFX_DATA_MAP
}
CRecordView本身提供了對下面四個命令的支持:
ID_RECORD_FIRST //滾動到記錄集的第一個記錄
ID_RECORD_LAST //滾動到記錄集的最后一個記錄
ID_RECORD_NEXT //前進一個記錄
ID_RECORD_PREV //后退一個記錄
CRecordView提供了OnMove成員函數處理這四個命令消息,OnMove函數對用戶是透明的,下面列出了OnMove的源代碼.
BOOL CRecordView::OnMove(UINT nIDMoveCommand)
{
CRecordset* pSet = OnGetRecordset();
if (pSet->CanUpdate())
{
pSet->Edit();
if (!UpdateData())
return TRUE;
pSet->Update();
}
switch (nIDMoveCommand)
{
case ID_RECORD_PREV:
pSet->MovePrev();
if (!pSet->IsBOF())
break;
case ID_RECORD_FIRST:
pSet->MoveFirst();
break;
case ID_RECORD_NEXT:
pSet->MoveNext();
if (!pSet->IsEOF())
break;
if (!pSet->CanScroll())
{
// clear out screen since we're sitting on EOF
pSet->SetFieldNull(NULL);
break;
}
case ID_RECORD_LAST:
pSet->MoveLast();
break;
default:
// Unexpected case value
ASSERT(FALSE);
}
// Show results of move operation
UpdateData(FALSE);
return TRUE;
}
在函數的開頭先調用CRecordset::Edit進入編輯模式,接著調用UpdateData將控件中的數據更新到記錄集對象的域數據成員中,然后調用CRecordset::Update將域數據成員的值寫入數據源.這說明OnMove在滾動記錄的同時會完成對原來記錄的修改.
在函數的中間有一個分支語句用來處理四個不同的命令,在這個分支語句中調用了CRecordset的各種用于滾動記錄的成員函數,這些函數在滾動到一個新的記錄時會把該記錄的內容設置到域數據成員中.在函數的末尾調用UpdateData(FALSE)把新的當前記錄的內容設置到表單的控件中。
由此可見,OnMove一來一回完成了兩次表單控件和數據源的數據交換過程.通過分析該函數,讀者可以學會在瀏覽記錄時如何控制DDX和DFX數據交換.
posted on 2009-07-20 11:16
Bluesea 閱讀(1007)
評論(0) 編輯 收藏 引用 所屬分類:
MFC