|
|
一、ADO简介
" p# V; R, M6 j4 D% S mADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。
5 p& |* `0 }+ L+ E$ S% |本文示例代码 & ~3 z) e4 {4 l0 Q# Z
+ S! q+ q# v* [( G二、基本流程
5 V5 ~: y k, J% _, J8 F) @+ O万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!
+ L' Q! T$ j; D* l( w$ g(1)初始化COM库,引入ADO库定义文件
% e5 K8 C! c* \* R6 k(2)用Connection对象连接数据库
* B% `" t6 X' A- V7 {(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 . r3 B L" S; {7 ]: D. ~
(4)使用完毕后关闭连接释放对象。
1 Q3 W7 J1 A6 d9 j# |: `- w& Z! q; V0 Q, }5 c4 b
准备工作: n" X* \% B2 @; B
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。 f& P6 j1 s9 P
下面我们将详细介绍上述步骤并给出相关代码。
- @0 p2 D! Y, v! Q【1】COM库的初始化
1 _% C! { A" v& M; ]3 `我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
& Y0 m' J2 q( Q. G/ |. ?. g9 c" _' ]9 N- h+ x3 _- H3 J
: t; W; j- q! p4 {/ s1 X0 iBOOL CADOTest1App::InitInstance()
0 Q& v ]/ i( h {
. K. K! ]. @" F( b( I* @ AfxOleInit();
- d+ n" T; E R% p: a( p) ? ......
1 p4 `, g5 m+ [& j! J4 U0 ]$ F5 c: G5 O
【2】用#import指令引入ADO类型库
% m8 q5 t: X9 H" O- ]我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) ! f$ \9 K. d, h" {. y4 M$ ^
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
" _: k5 x! j' p8 R) [! ^7 |这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
' q+ v& \( b# H V
" U6 _. f$ F, f l/ |. v几点说明:
" ~' a7 E3 I+ c6 ?(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改 : U/ d" j3 }5 D8 Y1 \9 }/ I
(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 " T9 G+ ~: H8 T
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned & N0 ~$ ^+ |. r3 Z0 S
) `3 B6 Y# q" P! e: N9 k* [$ W% N【3】创建Connection对象并连接数据库 4 T5 L, k) d# L" L' \1 K
首先我们需要添加一个指向Connection对象的指针:
; Z2 ~* {: G; E8 N5 D% m! n7 }_ConnectionPtr m_pConnection;
' g6 p6 w# d9 D/ M下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。 6 b6 @: F/ J: E i( ^
* `6 A8 Y: [* f- A+ }
+ H2 [+ D: \! J& k4 n0 W
BOOL CADOTest1Dlg::OnInitDialog()
; f) \; o% s' q5 t. { { 1 Y( h2 t% F8 l8 ]9 l7 }6 Q& ?7 u
CDialog::OnInitDialog(); - p7 A% ~, {2 l( O
HRESULT hr; , Q+ L& F+ ^$ E5 ~
try
" _& [) k2 f# j" t/ l- { { - f' U6 _0 T3 }+ N( T/ m
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
* B# l }0 i5 g$ v- y0 M if(SUCCEEDED(hr))
# i4 B/ {4 r7 G& U { ! I5 u2 k- W: Z9 C
hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 : o t( N( j' e4 d
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; } 7 P# W) W- T% T7 @3 R7 m
} ; {) Q1 l# d2 A; f! V/ C
catch(_com_error e)///捕捉异常 ; M0 m/ v# A8 J, O) t! p. G0 _/ J
{ 1 z% D) `7 y6 V; F4 z
CString errormessage;
2 F& y+ j. R" k" q3 E0 s errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); : n" C3 H v! I% Z. c& y
AfxMessageBox(errormessage);///显示错误信息
0 g4 |7 i' ]6 ]* I7 L9 R( W }
8 ~9 a3 j- o/ p [
- ~" ]0 a, P& V3 A在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型 % @( q- y( p! S2 e% t
HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options ) " \' e' t' P- A: E& ^
ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
5 C+ ~6 S/ W9 A' d: ]) Y% X- dOptions可以是如下几个常量: + i2 z8 {/ f7 T ]. h
adModeUnknown:缺省。当前的许可权未设置
( I& {6 b9 z* ~- TadModeRead:只读
. a' E1 Y8 Z2 ?) ]4 nadModeWrite:只写 8 f: Q7 p" [+ [
adModeReadWrite:可以读写 & U7 X4 K$ L% K7 H8 r' `) p6 a4 D
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
# I- y/ I* e0 [: b: J/ ?" b. yadModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
/ P$ ^8 k# f" g$ \% oadModeShareExclusive:阻止其它Connection对象打开连接
. \0 a* Y P4 qadModeShareDenyNone:允许其它程序或对象以任何权限建立连接 & N$ Z3 o* O) N' E( w/ R+ r
: E" B1 j! s% s5 O% c1 P* ~' N
我们给出一些常用的连接方式供大家参考:
5 h- |2 e0 E, G. G! J3 P3 U: ]+ W(1)通过JET数据库引擎对ACCESS2000数据库的连接 ' b1 G' s! n8 K
5 Z3 m; D0 C7 }% [0 O
m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);
( _' A# V$ a0 a
0 L& o5 C7 ~5 X9 {' p(2)通过DSN数据源对任何支持ODBC的数据库进行连接:
1 {& N3 ~( J: P& L0 J4 S9 J8 O% T5 X' Am_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown);
2 H! ~# ~" K( d t( h
( c5 E* g8 A7 E7 H% |(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
* N- Q+ f/ Z/ I/ E: y$ Y+ `+ i4 C. x( X6 a# q0 b$ X5 A8 c
其中Server是SQL服务器的名称,DATABASE是库的名称
7 r1 n8 [3 J2 U. c, b' E2 d5 |% { }9 G, P
Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State 5 p1 y/ i; H$ F1 l7 Q7 W$ m
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 $ k5 x# a7 r1 v r r1 Z& ^
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
6 b( Z" }) V; K3 _% f
+ `, B- b; `8 j/ y7 ?) l
) q( Z% z0 p* ?, g6 {( v: E1 nState属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如: % e7 W: `. |3 P+ E" D p+ G
if(m_pConnection->State)
% p9 I! E, n. C2 l/ J m_pConnection->Close(); ///如果已经打开了连接则关闭它
3 \3 G: |6 Q- ^8 Z+ {* n- t# W O2 y' s% o4 x8 m+ V6 R5 Z, T4 M4 d8 e
% R/ m+ k" @3 F9 s' M; a ?
【4】执行SQL命令并取得结果记录集 % B2 |* `3 Z: Q6 _! ]/ @
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
5 ~8 x" J( ]9 J) x" l* J并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
$ h, G \" T" V, ^5 dSQL命令的执行可以采用多种形式,下面我们一进行阐述。
3 V5 c# y! K7 X: [+ [ G2 \+ v. X% @; T
(1)利用Connection对象的Execute方法执行SQL命令
+ Q; Z _# v0 [- wExecute方法的原型如下所示: / k6 k2 V8 Q. L, H ~
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一: ! K9 ]5 J; a: S R& w2 g
adCmdText:表明CommandText是文本命令 ' U0 |0 p1 s. u4 [3 N, A4 }
adCmdTable:表明CommandText是一个表名 $ M6 X; s c4 c2 g6 q% P/ o
adCmdProc:表明CommandText是一个存储过程
; K& P0 L- v$ x a5 O4 aadCmdUnknown:未知 , ~" u; E$ F5 ^; \; y, h. y2 n
0 a( p5 e* e9 `4 A6 k' iExecute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected;
$ Y! g. B/ }6 ^5 G8 d. l ///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday
4 A: L% \7 `" y% k# I4 v, c. |& ] m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); A# h. k3 f q# t' i0 v) D& C
///往表格里面添加记录 6 y# I- G& g/ ~
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText);
6 x( X5 \$ C1 x7 d+ o ///将所有记录old字段的值加一
6 Y5 p2 o7 g4 ~5 g! {; ~9 v m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText); , ^2 _* ~! w' @; Z9 n
///执行SQL统计命令得到包含记录条数的记录集
. v6 y8 W' X _0 y1 n7 U m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText); 2 {2 N2 b; u7 U5 ] X3 d9 Y- p
_variant_t vIndex = (long)0; 4 ~9 a. R$ O8 c; z( D8 c# s
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量
, z8 D+ r9 k; {- K) y, t m_pRecordset->Close();///关闭记录集
n9 a$ A; s7 \2 A( A CString message; # S1 P5 i4 ]: h) J( F# `& ?
message.Format("共有%d条记录",vCount.lVal);
$ C2 K, z, K1 ` AfxMessageBox(message);///显示当前记录条数 + d1 c2 w# g+ k. {
3 n H# r% k5 ?: K5 |/ U
$ O6 N5 x+ f9 A6 }2 ]) _
(2)利用Command对象来执行SQL命令
$ Q- K; z/ w4 ]3 V0 A) _ _CommandPtr m_pCommand; 4 ^0 u0 Z! ]) B; ]( h
m_pCommand.CreateInstance("ADODB.Command"); 1 y( {; P. k" P3 l4 U; r! i4 q- a
_variant_t vNULL;
, |8 b) u* ?/ V+ W% z# z* S vNULL.vt = VT_ERROR; 7 a; F$ @4 A7 M# H
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
+ p2 a3 A# O( j% B$ E! N% a m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它
& O4 ~' A* `/ t/ ^ m_pCommand->CommandText = "SELECT * FROM users";///命令字串 5 @! v$ |5 z+ I4 `/ L
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集 9 l( u* @8 M m" F; f" M
! O5 X- p% _. C! \1 e1 w z) r在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
. D/ i5 O+ d0 ^- b- j0 p3 u6 G8 i$ N q8 a2 ~
, G8 S$ o# Y* Y6 i Y(3)直接用Recordset对象进行查询取得记录集 " O6 E5 n0 Q1 |" q
例如
0 H8 V1 X7 P( M: {* x& Z3 M, }& V/ T; ]
9 M8 R' Z/ t% k& v; f m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
b6 H, f6 d" V A" o1 Q& Q1 N
8 |5 b" s! G" b: POpen方法的原型是这样的: ' @; K3 D) s; Y' m
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
& X+ i4 h' M1 S: b, A其中:
2 J; _2 \2 w* I7 t/ Z①Source是数据查询字符串
7 a7 ^* {/ L w5 L* w8 i②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象) ! g7 m3 r) s5 \5 x$ v! P- O' l
③CursorType光标类型,它可以是以下值之一,请看这个枚举结构: ; q. o6 T9 @* ] J& h$ Q& H
enum CursorTypeEnum
/ Y# e" o% R* F/ z- e3 |{
4 g. `! G( C/ O `) y9 eadOpenUnspecified = -1,///不作特别指定
% W$ R: ~) b5 H* T f5 dadOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
4 Z$ J0 p6 w, I+ \# w6 qadOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。 6 J) z: I- }1 C8 I
adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
4 R; x, y, o2 t2 a0 ~ n* cadOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 ! I( F: |7 q9 U$ y/ i5 f R
}; 7 x7 b9 U6 |" F: R, ?
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构: 5 M2 K7 L. v; V" {
enum LockTypeEnum
; [0 r1 f- z$ |. J8 r7 P$ f; [1 ^{
. P3 T% L) Q+ C) W3 d8 T. e* `adLockUnspecified = -1,///未指定 * i9 ^; u* n- G0 d
adLockReadOnly = 1,///只读记录集 7 O5 `4 P% Z P* ~& z
adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
c8 A) n& W- Y) }6 XadLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
: C' J6 M) m" O: l0 tadLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。 8 Q- D) Z7 n+ K9 [" D8 C
};
1 T: P1 u& X) [" p$ I8 j5 Q, {⑤Options请参考本文中对Connection对象的Execute方法的介绍 ; |( P& V6 S3 K! g A# Z, ?
0 Z7 X6 X; Q* y
8 j+ m' E8 o/ D4 ?* K8 M- \
【5】记录集的遍历、更新
. V" e7 ^4 i% j/ ~8 G' b根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
8 g. @2 q2 P6 M4 |2 [. R2 L# a+ j s以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。
0 s9 H9 ` p+ W
7 A5 r, Z/ R! \/ S$ H; g' @! T( s! C$ x& Q' P
_variant_t vUsername,vBirthday,vID,vOld; ; Y( }8 v7 ~2 D( N' {, i( u" S8 Z
_RecordsetPtr m_pRecordset; 7 e5 x) G) M B
m_pRecordset.CreateInstance("ADODB.Recordset");
% @% h; v6 I3 `/ Q# r% V W u+ ym_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
0 g6 q7 c8 R. h2 D( E. f! swhile(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗? 4 \- J. e, j' l! p1 j! H8 ?; t6 n, H
{
; Z- i9 b6 `& S3 DvID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行 * M' W+ ?! B* p8 q" \& Q7 c
vUsername = m_pRecordset->GetCollect("username");///取得username字段的值
; q& L: T2 `- I. ` _vOld = m_pRecordset->GetCollect("old");
' N7 Y, K" m8 b, J$ JvBirthday = m_pRecordset->GetCollect("birthday");
( Z# L2 D+ C+ a8 w! E/ g' g///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
! X6 b P6 l. z8 f. N( R" n$ Y8 j! V: \if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
9 m* r5 A5 s% `3 Y S TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday);
, a% l7 s9 p3 @* l; h* d0 l% m4 vm_pRecordset->MoveNext();///移到下一条记录 * J0 H3 v7 {2 s/ I8 R/ e
}
6 z2 G& {9 p T0 [& z! Pm_pRecordset->MoveFirst();///移到首条记录 ! y, F9 o' n" E2 O) w8 g
m_pRecordset->Delete(adAffectCurrent);///删除当前记录 6 n" ^+ I# w9 M2 x
///添加三条新记录并赋值 # X: v6 d b& M* D( W! x
for(int i=0;i<3;i++)
1 I2 H0 \ I Y z: K+ D3 I1 Y{
; q3 C$ n0 `: z" U1 ~4 {m_pRecordset->AddNew();///添加新记录
. p1 r1 k3 K" f& s( r) Tm_pRecordset->PutCollect("ID",_variant_t((long)(i+10))); $ o5 [4 |: I% {3 c" A7 x
m_pRecordset->PutCollect("username",_variant_t("叶利钦")); ( U6 u0 V4 ?9 G5 s
m_pRecordset->PutCollect("old",_variant_t((long)71)); 1 A. Z9 ? v/ m N6 }2 p
m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15")); & a+ t3 v* J6 x6 E1 v2 W1 O2 v3 m
}
; G3 |1 C8 b. W2 h$ J7 Nm_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处 0 s/ R0 r1 W% c$ r1 ~/ o4 `7 [4 Z
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄 ' Q" N9 w% {6 W: ?& I( M( W
m_pRecordset->Update();///保存到库中
* M X4 g9 Y0 g3 F5 z! o7 p9 g8 C$ h8 u
【6】关闭记录集与连接
8 i* o2 z) j" a2 o记录集或连接都可以用Close方法来关闭
/ d$ M; l4 O5 t m_pRecordset->Close();///关闭记录集
- T; \, o* v0 e7 k( n m_pConnection->Close();///关闭连接
5 G) g+ Q. e9 A( W* m. L5 \
9 n1 |+ H$ c: c至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
q* ], T+ F% D2 B# u/ Q点这里下载示例代码
/ D6 o; b6 g( w& e+ s. d5 B5 R" v
后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|