|
一、ADO简介
7 s3 g2 a8 _" g) |4 N) f& AADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。 + H% ~5 |5 w' ~$ S* [
本文示例代码
' T" E7 e3 b' G" `; I x
6 P% X! k% n. u二、基本流程 0 e; i" \' q$ E8 n5 d5 [6 c# v
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧! 3 q0 e y8 Q, J' b: t: z8 g( s
(1)初始化COM库,引入ADO库定义文件
( }: p& u# U0 Q+ Y(2)用Connection对象连接数据库 ' r7 I3 R. `0 a1 c
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 3 }- Q! ]/ f+ I+ F" [
(4)使用完毕后关闭连接释放对象。 + [+ ^. ?) m& D4 F6 i+ Z, B
; P. y( s# h" d: l+ ~& ?7 h' V
准备工作: 3 }3 Q( N: L4 U: J4 P+ x
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
- \ j! |9 K& H7 `下面我们将详细介绍上述步骤并给出相关代码。
& W( n& Z7 g* p9 t5 w【1】COM库的初始化 4 V `8 ?. a2 E: X) q; j! Q3 _
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
, w* {6 I* i4 i! l3 [& J9 n& g& k$ K& P8 t6 ^1 k
, F5 }# y7 E+ N* q4 S. w4 _* |
BOOL CADOTest1App::InitInstance() 7 p% W$ a: q' ^: a9 I; a
{
/ E C6 I3 |) y7 e+ \- K8 W5 ?/ U AfxOleInit(); $ Z+ r+ B& b1 c& f( r
......
& Y, Z4 @5 W4 k8 Q3 c
; h% R' p N+ h1 w3 B【2】用#import指令引入ADO类型库 # v5 q% w1 Q" @4 M* p
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到)
+ O) A, N. r& C, U4 Q#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
6 y0 K, n; c5 U1 q# o7 _这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
. u' S: j& Q& w
% e$ D" w/ n7 J* a* d几点说明:
* E( S6 y$ w v% S3 u(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
7 Z/ p1 T) X# ]! Y1 r(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 ) U1 Z! h: W. g0 ]! W
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned
9 U8 V5 g1 e u4 [$ Q+ d. O& T7 w1 K
. a5 J- r2 m. c$ S4 D! b' t【3】创建Connection对象并连接数据库
% X- m7 m) o7 X首先我们需要添加一个指向Connection对象的指针: " T# C3 Z' f$ X! o% S4 k- ]
_ConnectionPtr m_pConnection;
5 m, @- ^2 ~. t% J& B' x+ w下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。
; S1 X( A% T Y% b. r# k0 }! _% _2 x& c Z" K) m) m
6 c: x) Z& v7 Q1 F4 H( m2 U. `
BOOL CADOTest1Dlg::OnInitDialog() ! }0 F- O& a& m4 `6 a: C3 X
{ - l& k- I) w. Z! E. H! D* Z- f% T
CDialog::OnInitDialog(); 5 l6 ^2 Q% R) ^
HRESULT hr;
0 {! H$ r, ^4 w2 I- I" P$ j try
! Q4 ]9 G7 K# U. t7 A {
6 P( d: G7 W6 l3 y2 ~3 U hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
: c* ?1 U9 m" {+ a- a if(SUCCEEDED(hr)) ' Z$ Z2 v5 E4 l2 e( i
{
, l9 M1 i! R3 h hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 " S; n$ B c& D6 [. K
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; } F" O- ^4 P1 S4 U' V1 R. l# u
}
& Z# i7 p5 z: b- l! ?( z. O catch(_com_error e)///捕捉异常 - m. ] C2 S% c1 c+ U/ L
{
T3 q- n; \! H3 T/ m/ N CString errormessage;
; X) f) D; |* z errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage());
X5 m; }* ` U- X AfxMessageBox(errormessage);///显示错误信息
2 e% b3 H. s8 N6 J/ S2 k {) V } 7 j; x! ]9 M: X) l, a' z D
+ l2 s+ Y+ X) l在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
0 M `& s" [ e9 mHRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
& ~1 J3 Q6 K. DConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权, $ m' c4 d8 Y/ @5 p* u
Options可以是如下几个常量:
0 k* ?+ m) ~% e, m `adModeUnknown:缺省。当前的许可权未设置 . B/ U. I% T ^) Q T- _
adModeRead:只读
' U! T, k& Q4 s$ {( ZadModeWrite:只写 % G% e; E. r# i1 N
adModeReadWrite:可以读写 7 f6 G9 r) y2 B, _
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
3 s% E2 O& m- Y7 g# _& ~* dadModeShareDenyWrite:阻止其它Connection对象以写权限打开连接 , L" R3 [, F7 B$ H- u' b% K' X
adModeShareExclusive:阻止其它Connection对象打开连接
4 T9 o" a0 t: C" R& d& W' ?adModeShareDenyNone:允许其它程序或对象以任何权限建立连接 8 M' e: ]) o3 W7 w3 _
, x8 K6 t5 M# G$ e& P7 P5 j* c6 w- l我们给出一些常用的连接方式供大家参考:
" W/ z+ o) s+ i7 K2 ~+ k6 O( \(1)通过JET数据库引擎对ACCESS2000数据库的连接 4 ^# f6 h' ]' B4 N X- z1 y4 v
% M+ y# k. e; U5 X8 g4 ]7 e
m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);
3 R/ d: R+ ?2 B# g1 a1 Q8 i2 C9 Q3 g9 x
(2)通过DSN数据源对任何支持ODBC的数据库进行连接: 8 [( ^/ u ?$ }' O
m_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); , ~; j+ E" |7 x! f. u6 V4 C
; @% H3 l+ X( K. b, V" P6 H
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
\2 @/ i3 s* ]7 a
( A/ |! B' K- a4 e: b9 b, o! Z其中Server是SQL服务器的名称,DATABASE是库的名称
- ~8 V9 j2 D2 R. V1 s C3 O1 U9 ^9 H7 w$ L- w1 b; d+ M% O% r
Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State 4 L F. a( a1 |/ T: g1 ?3 z
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 % D) D) L) P4 A# B' b2 u
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
* @" U2 o- a* ~) a h
% d) V: q! M( h( w4 a1 @% N
B0 ?# w2 G% b7 [+ f* {' v" \State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如: * G4 h. F1 b5 h* q& U( U1 j
if(m_pConnection->State) 6 H4 m; r+ h- M/ i; A
m_pConnection->Close(); ///如果已经打开了连接则关闭它
5 d5 z0 \/ o8 b, p4 e! c% {" g& @. e4 R& b5 |+ R9 ~8 T8 w0 I
7 t* s+ k5 t, b) c1 P2 Y
【4】执行SQL命令并取得结果记录集
: @# n/ I1 P* E9 I' c为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
% P5 c3 q) f2 F, }) d) y7 C并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
4 t$ U% W" ?: t, m/ NSQL命令的执行可以采用多种形式,下面我们一进行阐述。 5 V: Z q1 ^1 `( z( v
5 i1 i. `+ F' f( K/ s6 u
(1)利用Connection对象的Execute方法执行SQL命令 ! ?0 ]: {3 t2 R% i
Execute方法的原型如下所示:
; P+ y1 E+ ~" Z_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
7 I' ]. r5 I- W# R# }7 u/ iadCmdText:表明CommandText是文本命令 ; S: _9 K4 o. t. [" u* p
adCmdTable:表明CommandText是一个表名 ! H0 \/ g% n. T1 N$ x
adCmdProc:表明CommandText是一个存储过程 , G# [5 r1 ]2 \
adCmdUnknown:未知
2 Y3 S" l7 Q9 m" H5 |8 _4 a8 a( x5 J# S6 q6 }8 q; x4 g
Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected;
6 T$ f# H- _0 q2 |2 w/ I ///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday ) y6 o) {! J( @
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText);
+ o5 G3 d% ]$ t) g6 O! a+ r2 j ///往表格里面添加记录
9 A) k& q( q- x% m3 Z5 O0 k m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText);
9 \* t, x1 n, G" ^0 o, A5 s& I2 S ///将所有记录old字段的值加一 ( c: ]" A2 Q# E4 P; j
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText); " V3 [; b- e1 b( ? Q
///执行SQL统计命令得到包含记录条数的记录集 / Z! i. C) q4 i# W4 n# X( x; g
m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText); 9 o- \6 ?) {7 H- k0 ?9 u0 t
_variant_t vIndex = (long)0; 7 H! h) U- b" }6 y- ?/ Y
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量 , D8 N+ W1 ^ g
m_pRecordset->Close();///关闭记录集
8 E& s& w1 A" j9 W% }: [) }8 j CString message;
' J* `8 \$ B! i9 R6 G message.Format("共有%d条记录",vCount.lVal); - y( d& h* ~1 D0 r7 H3 V2 z% n
AfxMessageBox(message);///显示当前记录条数
- O3 i [ \2 e" Q
& i: c3 h5 W" c$ c( p' r) O
" B1 e- [7 {' e" X+ D9 L3 V(2)利用Command对象来执行SQL命令
' X4 y( _! r# |/ r1 H0 o _CommandPtr m_pCommand; 2 @& M* z. q/ p& v. O2 J
m_pCommand.CreateInstance("ADODB.Command"); 3 D" a+ C7 l: M; P4 W
_variant_t vNULL;
& ]3 _# H; o t `2 ]/ P. } vNULL.vt = VT_ERROR;
% t3 s7 s/ l: ?7 p. O' K vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
* u5 W6 n* @! ?3 h+ g0 l* D, x4 `- E m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它 ( n7 P: [: Z8 a8 c& g5 F
m_pCommand->CommandText = "SELECT * FROM users";///命令字串 ) w, }0 s. t9 l E- G8 N( M
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
I r# n' G5 I. @+ L* y
4 `1 v, }- C1 u! o' U# ^( n& A在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。 : ^+ x; B( g! t- J- s4 v1 I1 y
( B8 W7 A( k4 o6 F
0 w7 U* B: }+ U ^7 g. ~3 c
(3)直接用Recordset对象进行查询取得记录集 5 v; ` y7 ^) q; s+ p
例如
7 W9 j; S2 Q0 [; a- L6 O5 B
, f+ ?- Y# ], a6 Y! u m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); / z5 A0 o" x9 e* G$ _# \
# K7 i4 H' r+ A; `) n: l0 VOpen方法的原型是这样的:
( f# y/ k) u2 E5 v/ n$ C" _HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
& x7 N) d& [. [8 H其中: 7 B0 d! {4 R' Q3 } g- ~. P
①Source是数据查询字符串
" y' n7 t' H+ |②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
+ t, Y% H5 Z) g/ ~3 r- B. W9 y③CursorType光标类型,它可以是以下值之一,请看这个枚举结构: 8 U3 ^. g0 d6 b% M% b6 [, z
enum CursorTypeEnum
& g" y3 h& ?' [7 y* z. g( t, F{
6 s5 V" D; B' GadOpenUnspecified = -1,///不作特别指定
) A. g7 N$ w$ I8 _! g5 o3 v8 @adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用 8 R8 }3 b1 d) o5 S7 z6 L
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
; ]( L, @; _6 H! H" j% ~, UadOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
; t& c) i* i# z: X& H4 g, g7 M D6 padOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 " s# o8 r- v1 @/ N( L
}; # E/ l7 a' q3 F/ r# P; o
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构: . v3 |1 x' x: W" @/ g3 b
enum LockTypeEnum
$ s& j+ i/ k2 L) H& X _6 `{ 9 Q5 D! v" w1 L4 p/ i
adLockUnspecified = -1,///未指定 k9 j7 B+ a" x6 _' y8 } v, J! n
adLockReadOnly = 1,///只读记录集
. G8 w3 x- ~9 h" \9 HadLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 . V/ m( `! a* F+ ?! O0 E' Y
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
$ L7 r5 R* Q2 u/ g% V6 ^5 {adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。 V1 f6 N# T4 ~+ c2 [ l
};
+ w, E F# B& v$ [5 @⑤Options请参考本文中对Connection对象的Execute方法的介绍
! B3 ~+ K" F- q- i+ Y( k$ l W1 F, k3 U; t7 W' G: G
( P" b# f% `) _8 ^: Y/ G- @9 Z【5】记录集的遍历、更新 % V6 M& K" b8 X5 P1 v
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
" D0 n' i1 w+ N以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。 , _; v4 n; }. n4 p4 t" S( E
9 g6 `/ U/ F8 K3 s* V) f4 H. K" C) V0 m% h( z
_variant_t vUsername,vBirthday,vID,vOld; + y8 Q& c6 e% ?) O, \ R6 r1 e
_RecordsetPtr m_pRecordset; / i, Q" T- z4 w& A
m_pRecordset.CreateInstance("ADODB.Recordset"); . T& {3 Q: v! @! Z2 L' I
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
3 d. L" _9 R- o$ P+ m( D+ E' Qwhile(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗?
0 |0 A* ]+ t) C- N, J0 v2 P* P0 G{
- [2 Y8 J7 z$ u1 o2 m: MvID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
d) V) [. T: C) e! H6 H* f, x0 U! ?vUsername = m_pRecordset->GetCollect("username");///取得username字段的值
" g- ]" k; q7 z; B: M" J# OvOld = m_pRecordset->GetCollect("old");
; ]! K) Q$ T8 y1 [5 F) \) m0 Y: cvBirthday = m_pRecordset->GetCollect("birthday");
1 ]: l7 A" N6 u4 j6 y+ s///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
1 p; s1 K# F$ `; Y7 Eif(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
7 Z1 ?: S+ l9 ?3 u* L& ~ TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday);
- a( M9 v( m4 Sm_pRecordset->MoveNext();///移到下一条记录 $ P& Q; R r/ D" C+ S; Z# G
}
7 \% e4 N6 @) b+ P( U( K; Xm_pRecordset->MoveFirst();///移到首条记录
, i6 `8 |1 W$ U, W$ dm_pRecordset->Delete(adAffectCurrent);///删除当前记录 % ? v, _! d0 N) ^' F. X
///添加三条新记录并赋值 0 | ~% u; ?6 F1 s" Y- O" Q& s
for(int i=0;i<3;i++) ; T& H1 _+ v6 q" f$ I
{
: o/ X- }/ q7 ^# B9 M3 B. |: tm_pRecordset->AddNew();///添加新记录 ' o+ F- h6 y% \6 C; o
m_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
" c N9 }: n9 P( _0 ]% I' ?4 Sm_pRecordset->PutCollect("username",_variant_t("叶利钦"));
0 b6 M( ^, v- zm_pRecordset->PutCollect("old",_variant_t((long)71));
1 u b p2 T5 I/ T: ~0 k4 bm_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
9 l7 R. w4 C" p2 u& { a}
j6 _# ?. | N. }m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处
* w) A" N9 Z/ M/ N c3 `8 D) Tm_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄
( U* {: s2 E% z" r/ A+ u) fm_pRecordset->Update();///保存到库中
. ^; }) ?, u+ [! u! q/ E' V' u P
" J" A7 \1 \# o& ]- g( }# e【6】关闭记录集与连接
; \; u8 i2 F! t9 @" i3 j& V记录集或连接都可以用Close方法来关闭
. _9 ]* f6 _- t- e) D% k3 @/ n m_pRecordset->Close();///关闭记录集 / E4 v) F. O* ?' X d2 k) ?- i9 n% n
m_pConnection->Close();///关闭连接 ) O" r; l5 {; x
5 Z! r2 {8 I$ S! i至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
# I' k& ]8 h* z# b点这里下载示例代码 6 q- e$ e" b. q t8 k/ l
8 `% n" z, H' Y5 E, k8 S
后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|