|
|
一、ADO简介
4 C8 N G- m% m0 _ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。
# s2 o# Z" }4 n( [+ n/ S' J本文示例代码 - o! `' e9 m" z4 w
6 u0 `/ X, i; H; {* S% \# h
二、基本流程 - P# h9 \! ^3 `. {6 c6 J# [. E
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧! 1 z' i( ~- b* u0 t0 Z# |
(1)初始化COM库,引入ADO库定义文件 0 R- [2 a1 @- n) z- V# R/ G
(2)用Connection对象连接数据库
$ q$ ~9 ~9 O( R(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 % J5 `' y7 e: M, F1 H
(4)使用完毕后关闭连接释放对象。
1 T7 v5 L* K) R5 G8 q8 Y1 [
9 w: M1 k) L) \6 S0 N6 b准备工作: - Z3 f! A) V' i% p+ @
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
5 n, d* h* w' D9 J+ e下面我们将详细介绍上述步骤并给出相关代码。 ( |" R; T# l0 p2 k* r- F, b
【1】COM库的初始化
X* Q: T4 k4 H, U U% ?. J我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
# n$ {! W6 x/ D9 y. f8 f$ W) N0 m: \ u5 U# n8 \4 o; M
: E+ ~3 h% X9 [0 X4 p. g/ P
BOOL CADOTest1App::InitInstance() ' ^/ c. c$ l8 p- A D) T
{
" P6 v0 |1 G& _4 N$ h* w4 V$ ?' O AfxOleInit();
+ { \! E! O! l. M# g c ...... ! H% L3 A; H" s5 k9 [& P
! D" G1 k/ S0 Z; L: H" i. ]
【2】用#import指令引入ADO类型库 + }2 V" k+ A7 i
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到)
9 y( J- [) W; q$ f1 ?#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
" c3 e/ X* R- H这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
+ z* f4 C$ {* \* T) Y3 i. a8 p& U, |& n4 k
几点说明: 0 n* U6 W, L/ y6 T6 x8 J$ F
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改 ( M8 y& p% K+ ?3 Y
(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。
@" e3 g/ U9 @/ C+ j1 z" lmsado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned # d+ Y/ M% i. q6 Q
% M: t: a8 l, T9 Y$ ]8 p' ?【3】创建Connection对象并连接数据库 5 o+ I; V2 W2 n; Q9 m0 k2 y
首先我们需要添加一个指向Connection对象的指针:
/ O( [7 L3 s! z/ Y_ConnectionPtr m_pConnection;
1 L# D$ d8 Q/ C3 m4 }" m; [下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。 9 s' i# x, t. o) J5 }' Z
6 _1 s* H7 q! Y/ n/ V% D; g* s7 V, T' G3 S
BOOL CADOTest1Dlg::OnInitDialog()
. ~. C9 V, g+ V J$ h7 C' b { ( U: C% o1 P; c# u$ h k
CDialog::OnInitDialog();
2 z: \6 P2 o: p4 |# {9 z/ F HRESULT hr; ; q# \+ k8 E1 t$ r% a
try
# ^9 d9 z- i, _* p. w2 j { 5 B7 A2 B8 k3 i
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
/ \$ Q' a% o( j6 D8 y if(SUCCEEDED(hr))
( k, I. I( K2 ]; I { + `! b6 S2 y+ i* b% @+ g
hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库
( E' s% R; t. Y, _ ///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; }
% t% H; `" v/ \4 ?2 ] }
+ Q+ G+ V* D* i3 e$ @' Y0 S2 L catch(_com_error e)///捕捉异常
0 ^0 m8 i" p# o9 Z% L {
, W1 \9 c6 f% M3 C CString errormessage;
* x1 r& {+ Q1 Y9 X4 J errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); ! S$ T) I7 f4 y' j/ h3 m
AfxMessageBox(errormessage);///显示错误信息 + e2 X" c! [0 [$ U' E& V
}
* m, Z) @ v$ L2 f( H9 n9 G9 y/ e: ]
% C6 @6 q- k6 w v. x7 y& p在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
8 H& M# ]2 \0 B) _HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
1 I! `% i$ l' D8 ^ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权, & ?; B/ ?5 w0 `7 A; r5 ^8 B
Options可以是如下几个常量: 8 }2 a5 Q$ d3 R6 u8 `4 c8 @5 j
adModeUnknown:缺省。当前的许可权未设置 3 U; H7 B- y Y6 c! N
adModeRead:只读
. p& J7 J7 w( ^* s5 F- A4 badModeWrite:只写 , N9 T$ R# |# ?9 G6 h! O! B
adModeReadWrite:可以读写
. }/ Q( o8 y7 h+ I/ l- badModeShareDenyRead:阻止其它Connection对象以读权限打开连接 & F# V' E$ C7 s! b/ Q( S2 |
adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
; D# O- W9 d& u# P& X& U+ `- `7 xadModeShareExclusive:阻止其它Connection对象打开连接 4 S) \6 K* w$ l
adModeShareDenyNone:允许其它程序或对象以任何权限建立连接 ; C- [) X; u# s) E& A1 m* B
- q: T: f# H6 S J) s我们给出一些常用的连接方式供大家参考:
! a7 y6 p7 I" Q# d9 Q9 @7 D(1)通过JET数据库引擎对ACCESS2000数据库的连接
+ e& ?7 {/ D9 [! `1 E0 c% k/ S+ a
3 _5 o* r+ J! z7 C, bm_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown); 4 ^, T3 v/ A! e
/ s Z1 ], C+ O(2)通过DSN数据源对任何支持ODBC的数据库进行连接:
) b! j+ j. }) A! P- p: Jm_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); 7 {2 H; y: x; l# L0 o8 s( I9 J* B7 c2 N
* ~! E: s" P5 ^/ ]/ _
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
' z w4 X9 F/ S/ p* _$ j6 w6 c$ H+ k
其中Server是SQL服务器的名称,DATABASE是库的名称
; k X; w* G; T7 ^% l4 s6 A- z& n: C- n7 r& }1 B3 D( R2 V
Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State
- f$ I$ G& L9 a; J9 GConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 8 s ~0 ^% ?8 ^, r8 S
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
! Q E9 ?3 v1 k% \% F+ P0 _, x! F- T# M0 W! ~3 \% x
. C: _0 V) K6 e' c- V3 I. Q
State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
% D+ @( O" f/ |if(m_pConnection->State) 6 j! r9 j7 j* Y" c3 h( ^8 E; J" i6 f
m_pConnection->Close(); ///如果已经打开了连接则关闭它 9 A0 r/ u6 Y- }
# `% G* l. c; K% w# l z/ Y9 ^
; w3 A/ u& ?1 K7 t" { _【4】执行SQL命令并取得结果记录集 4 e- Q% i' F1 K
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset; - Y& z# ]. i: c m
并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset"); ( c, r S9 C- `" R% \
SQL命令的执行可以采用多种形式,下面我们一进行阐述。 2 w+ T/ G& `4 U& W5 Q
+ a! d/ ]9 a0 \7 y(1)利用Connection对象的Execute方法执行SQL命令 7 A1 q3 k' N0 o, y* K
Execute方法的原型如下所示: 9 @8 ~' O/ a! o9 P5 c" A
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
6 ?, d: o F0 F$ CadCmdText:表明CommandText是文本命令 . z& X% l+ v' s5 ]1 T7 h7 q2 L
adCmdTable:表明CommandText是一个表名
- I5 u* F4 v6 L. ]+ N5 JadCmdProc:表明CommandText是一个存储过程 & j' h% v1 T, U1 b o- S& W
adCmdUnknown:未知
# h% C4 E# s" B# b. _" ?+ j
8 I. y. ~" j9 \4 q }0 J0 z- ]7 yExecute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected;
. `. I% P% t) d4 Y. F. c: L5 T. j ///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday
9 _& R& M% E4 }+ j m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); , O1 y" y' g* _+ Q a- j3 ~
///往表格里面添加记录 1 ]" l6 Y5 ^8 M# h' Z
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText);
8 K: U# h/ v: E7 C: f ///将所有记录old字段的值加一 ) i6 s% {3 P% n) w+ S* p
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText);
' n: Y# _- E ~ ///执行SQL统计命令得到包含记录条数的记录集
, |: e1 N$ g0 D* _6 t m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
0 J% U# f' |& b; F _variant_t vIndex = (long)0; 8 E+ p6 Q, U" M# P" T' f
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量 % u, z" S) i- z, R8 {$ ]8 t9 P& |: h
m_pRecordset->Close();///关闭记录集
! u" E9 u& x) w w8 d) S" ^7 W" A- n CString message;
8 S/ _5 y0 ~* R1 h9 X9 j message.Format("共有%d条记录",vCount.lVal); k9 ?8 x5 L: \, T
AfxMessageBox(message);///显示当前记录条数
8 A& l' l. }: J) U- B4 E! K3 r
( m: R3 P6 u5 S5 P5 e/ g c t! h1 x2 u9 l: T+ s
(2)利用Command对象来执行SQL命令 7 a3 j2 e7 p; p1 u$ O$ W
_CommandPtr m_pCommand;
; X" W: R- ]% q2 N. f m_pCommand.CreateInstance("ADODB.Command"); , H* i9 c/ n( G( ^; U" \2 X
_variant_t vNULL;
7 U# L' `5 x8 |/ l vNULL.vt = VT_ERROR; + k# m; b6 N: a
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数 2 z- g: B" ?# e" o
m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它 - V* _. G1 O/ W3 w9 `8 X% Y' J* M
m_pCommand->CommandText = "SELECT * FROM users";///命令字串
; [- e* e& C- A3 u m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
. H: y/ X' Y3 B- h; E# j D
4 Y# `7 E0 e3 ^+ c' J在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
0 v$ X4 f( J; `5 T2 B' B% G* |9 R8 @6 K' Z
& m; Z- j7 m7 `* p( @(3)直接用Recordset对象进行查询取得记录集
% g! Q2 p3 n; H% U: S' p& I例如 4 M+ @7 n4 Y9 q" P1 S2 ^. q E
. }" I5 }5 v8 N7 J# T. x6 y m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
5 J* z. j( O8 ^0 @+ o2 S5 ]) K* C" n* z) c+ q+ }
Open方法的原型是这样的: # |' C) Y Y1 z( ^
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
/ ?5 }1 W9 h' {* s5 c: o其中: : e# L7 ]; \6 h0 e
①Source是数据查询字符串 0 J* T' G) D) l5 X4 d+ D" p
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象) / K- u" k0 b2 c& D5 A
③CursorType光标类型,它可以是以下值之一,请看这个枚举结构:
. C' I/ W1 i! w- R; Renum CursorTypeEnum - m4 |' V& ]6 v% P# P1 h
{
0 b v& g0 ^. n" {4 k# ladOpenUnspecified = -1,///不作特别指定
% m) P0 `* X+ G( i9 U# W2 U# x1 nadOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
% M' G5 J) ]; O. vadOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
) J+ E0 e& f. y- G4 u+ C8 s sadOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。 8 f0 d% W! _/ I6 c
adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 * P9 Y1 G. p8 L% \) b7 X( ^; h; ^
}; + \$ a6 F4 a2 C
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构: 9 X l3 j$ A* N
enum LockTypeEnum 9 g3 X Q) i. C1 D( R' O( x( s
{
5 b5 Q/ e5 Q: o: f% @9 f4 PadLockUnspecified = -1,///未指定 . ~0 ^4 s: O x5 f8 |
adLockReadOnly = 1,///只读记录集 ' Q/ R. v; G% a/ x* ^) v O2 R
adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
, g" k: ~0 z6 b. A$ Q9 t; JadLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作 % b E# C, ~+ L, E2 Z- V6 x
adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。 . A3 y n! [: [$ _4 o
}; / ~- ?5 e, h; D V' e! }
⑤Options请参考本文中对Connection对象的Execute方法的介绍
; ^ q, `1 X, ]2 z6 W7 H
5 t; Q! I( p! |1 J' ?6 Q0 I
8 q( v: e3 W g, W' y" d7 [ f+ _【5】记录集的遍历、更新 ( Z0 A0 k4 N; e" X( z
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
% g8 n) s# w) A9 ~以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。 ! s, r4 ~/ J% ]
5 p! m4 z" a$ @( G: Y: b* l! k1 M, ?9 `' |
, i9 @3 U) u4 I_variant_t vUsername,vBirthday,vID,vOld;
. v, y& G6 P1 Y$ t; s% u; a+ R_RecordsetPtr m_pRecordset; ) j( f% M6 J9 s) v+ m
m_pRecordset.CreateInstance("ADODB.Recordset"); 2 M5 O1 |2 A- A! i1 o
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
/ |# U& W- N' m- u9 pwhile(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗?
8 B2 u- ]3 R) z2 _7 Q5 _2 X{ q4 ^2 ~3 o+ a% ]' g/ @, i
vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行 " x" m6 w- e$ ~8 F$ Q+ G0 b8 A
vUsername = m_pRecordset->GetCollect("username");///取得username字段的值
* ~) }/ Q" o4 N8 [vOld = m_pRecordset->GetCollect("old"); 6 F1 J7 o* {/ t
vBirthday = m_pRecordset->GetCollect("birthday"); ; S& q8 C5 d6 I0 Y) O
///在DEBUG方式下的OUTPUT窗口输出记录集中的记录 + G$ @$ j6 L5 S5 t9 ~9 v$ W
if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL) / l3 o! k5 ~8 h% }0 z( b
TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); * r, }6 ~+ g9 E0 ?" |% J
m_pRecordset->MoveNext();///移到下一条记录 * j3 z( j: ~' S
} ) O Y7 ?. J3 ~+ ~2 {1 N
m_pRecordset->MoveFirst();///移到首条记录 0 g& t+ z% t3 W; H1 x, s
m_pRecordset->Delete(adAffectCurrent);///删除当前记录 5 D# |+ y9 ]$ @& ]% J- {
///添加三条新记录并赋值
& A$ H+ n2 j6 v$ i9 a* C6 Tfor(int i=0;i<3;i++) / x" f& ^. G6 f( }( V- l
{
9 N/ h+ ]9 e) K k1 dm_pRecordset->AddNew();///添加新记录
$ {: h+ N {: k' r& \, Pm_pRecordset->PutCollect("ID",_variant_t((long)(i+10))); 0 _, v$ |9 X# Q9 Q; ]: a9 d1 X9 ^
m_pRecordset->PutCollect("username",_variant_t("叶利钦"));
; k; o o- `! x4 Xm_pRecordset->PutCollect("old",_variant_t((long)71));
) i. L" d9 s1 Y" E; l6 em_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
9 c" y/ x( D2 o/ d( W! u* ?# \} $ u2 C' u: z3 [7 M( O
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处
' L6 }# C8 g4 d tm_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄
4 o8 P0 a2 G/ F$ N, A& Om_pRecordset->Update();///保存到库中 M' X9 O$ M0 @
! b; n+ n" ]# |, M+ L7 t+ e$ g【6】关闭记录集与连接 . I% t) Q5 o5 C9 E# u* S
记录集或连接都可以用Close方法来关闭 / ?. @7 Y( K4 P% _( ~+ c7 M# O8 x! t
m_pRecordset->Close();///关闭记录集
' d2 L1 z* k' @- s! h0 Q m_pConnection->Close();///关闭连接
% o5 Q7 z' i6 Y+ D8 o- N! `+ U, k l, z& ]; z" z
至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
( O' ?, ]; w: I3 ^$ v) t点这里下载示例代码 , g" }4 [/ c3 ]! o% w/ L }' s7 @
0 }0 i8 ^: [: K; R! V: s后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|