|
|
一、ADO简介 ; e- i" P) p% p" @: t- a) N, x
ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。
5 M9 }0 W( a( F! n9 r本文示例代码 $ w1 j/ d& W3 o0 Q
( f7 |) N7 c7 E4 E' e8 I
二、基本流程
! }6 d) Z! y' \ L( y7 f L. f万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧! 6 E; S z$ Z1 g6 b
(1)初始化COM库,引入ADO库定义文件
8 ]& z9 g7 R' A, Q(2)用Connection对象连接数据库 $ T3 W* K1 T; K+ Y9 B# c0 i8 [3 `
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。
" p4 x" J) r; a/ x- x" |. ^+ o$ r" t(4)使用完毕后关闭连接释放对象。
9 b8 A3 @6 E4 H* S8 J9 M5 D
; q, j! F0 V& R; q( G准备工作:
# W- d" _/ J/ [3 D m j: ]0 B# G为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
" ^( ]9 ^2 K/ d下面我们将详细介绍上述步骤并给出相关代码。
* w1 E! M6 {) T( X& Q* I【1】COM库的初始化 7 U, m4 e- O) v# w5 ]# T, b. c+ }
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
7 g- P" [% K. W. z' G6 X [- z4 w$ L1 A" g$ Y4 x1 ~
- E, [& ^: G% D- u7 M
BOOL CADOTest1App::InitInstance()
8 ~( R) F& v% R# M3 w7 U1 p" t {
& Q5 D' I* S; L4 t8 Z2 w AfxOleInit(); $ g0 t3 }$ C6 {' D: M2 A
...... 3 }5 @. v: k; S {1 M, d
* A* H9 f5 D. }. I% C& E
【2】用#import指令引入ADO类型库
" i( T# N3 ~6 F* q1 A, A我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) 4 K4 x: R$ i' t0 f& X# ?
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") , B1 j1 ]2 ^$ i
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
6 o1 N$ O+ f H# ~, w: d& b4 O
4 ?( D. n+ ?+ N几点说明:
* P( R( A/ u6 Y2 x# `(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改 9 E- K5 Y& p$ @; [0 s
(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 5 }% G' f; x& s! k
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned 4 u: C& H$ _( s5 D4 k
* L9 M5 h4 p' B* B% a2 i【3】创建Connection对象并连接数据库
2 x9 C/ c# }5 n; p. j; v# d) C8 r. o5 U首先我们需要添加一个指向Connection对象的指针:
. M& v; |* i1 b1 k2 @+ v. \' J_ConnectionPtr m_pConnection; 4 b( e) n( A' c3 a) L% h
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。 / {2 a7 {4 V) B5 d1 R" n' W; B" S
$ w) t! y0 ^, V1 e9 |/ c" N/ U& @+ {# O- H9 x& S7 W1 @& D, f
BOOL CADOTest1Dlg::OnInitDialog()
3 Y) I) |# ] t% e4 [# ?* z+ J {
5 e% } _4 i0 \* T; o3 E8 Q7 H- | CDialog::OnInitDialog();
5 b h2 i( _2 C0 [0 P) t HRESULT hr; ! i% T6 d$ M1 {. l9 \
try 1 m' T7 t6 j! e$ H& H
{ / [- S3 D R% g5 B, |
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
4 w7 H% x# }$ z9 ]2 t if(SUCCEEDED(hr))
* N; a8 b9 I! J, G {
0 s5 g: U' C b' { hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库
; e$ Y! ]& D) w! c4 L ///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; } . S W( X( N* e/ Y
} 5 T' y; q: F, l% n# h
catch(_com_error e)///捕捉异常
x, t- l* d# w {
4 u+ p+ Q3 i) Z, b/ N- o3 }. j CString errormessage; 1 d7 O. Y# i- f$ h1 R
errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); ( o1 P1 B3 Z: w
AfxMessageBox(errormessage);///显示错误信息 $ g6 |; t4 Q0 W
} , s5 s1 q. t3 e7 _
( @, c# O7 y) b
在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
! a ?! d$ h2 p' X+ j5 e9 O) Y- }! YHRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
9 B z& n8 ]+ W" l# o* zConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权, ; C7 L2 o1 u- j4 |
Options可以是如下几个常量: 2 X7 Q' K5 A! c7 N# S/ S
adModeUnknown:缺省。当前的许可权未设置 2 {7 j! x9 L' D/ {4 y7 {
adModeRead:只读 * _, T, o- e- }0 T: E8 w$ I5 \
adModeWrite:只写
7 A- F" t* P+ q# w- \- `adModeReadWrite:可以读写
& u8 v+ j6 ^% A `, u7 e5 l x! EadModeShareDenyRead:阻止其它Connection对象以读权限打开连接 # I6 D& Y& v y7 P, S( w% h; ~6 O
adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
/ [4 `0 f9 T1 p4 G- qadModeShareExclusive:阻止其它Connection对象打开连接
% n( V8 L6 d1 c9 J2 @6 ?1 aadModeShareDenyNone:允许其它程序或对象以任何权限建立连接
3 \5 ~- a; j0 O9 l8 }* }- l1 Z+ k6 ~ z5 w' T+ w$ ]
我们给出一些常用的连接方式供大家参考: " E# f+ Q$ Z; g' Z1 v
(1)通过JET数据库引擎对ACCESS2000数据库的连接
1 s$ ~: F7 A/ A6 m. h( N t& g" s. Q9 F" W% ` M$ e I
m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown); v/ R( e4 L1 O& t( z$ C5 S2 |
4 p7 j/ G8 h+ i# |( h! W(2)通过DSN数据源对任何支持ODBC的数据库进行连接:
5 `; k$ w# I, nm_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown);
0 O" {8 J6 p* b) v/ D% Q- |0 b. A: D# m
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
- a: R3 J9 k `8 Z
# {9 s: B- e# J( w- F, t$ p" L, N其中Server是SQL服务器的名称,DATABASE是库的名称 8 {4 W' C9 p5 s! ~5 Z
, h1 w9 H- S1 k! y1 z) K% i: D! {Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State - L4 I- B6 T% M$ l0 j' a
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒
6 z( K% {- |* N% cm_pConnection->Open("Data Source=adotest;","","",adModeUnknown); - T" m5 s' Q9 A
5 v9 c8 ^6 E( e K& |6 x( X; M1 J4 I. I# c
State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如: 0 ~( d, { S& ]% r4 p% J' l n* K
if(m_pConnection->State)
& i/ ]+ O" c4 \! L m_pConnection->Close(); ///如果已经打开了连接则关闭它
7 g: T2 r, v! R+ e+ u
$ q6 m9 Z, f: o+ D
8 |7 ~" X/ R6 @5 I【4】执行SQL命令并取得结果记录集
) M) _, ~. H L, I! d, z为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset; 6 ^9 v$ L) Z$ s! ], }
并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset"); * Z% U& Z2 R/ n: \( Z& E
SQL命令的执行可以采用多种形式,下面我们一进行阐述。
3 s! _5 q/ r7 ?1 V
. H( r$ z" y: n(1)利用Connection对象的Execute方法执行SQL命令
7 K+ K3 T5 Q0 h' G* S8 TExecute方法的原型如下所示:
7 H3 y+ y# C4 y1 n_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
' x9 u: o+ @) Q* ] ]* ^adCmdText:表明CommandText是文本命令 3 S6 M- a0 r) V( v
adCmdTable:表明CommandText是一个表名 + ]! r c U' _5 t
adCmdProc:表明CommandText是一个存储过程
1 P7 v# I2 N4 H9 a/ KadCmdUnknown:未知
- D# q' t4 o8 ~
2 J% Y9 g4 j9 h0 {Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected;
4 }9 T; F: h' r4 ]- ] ///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday
; c7 U/ Z( Z% D+ F/ d: x m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); / V0 X4 K* `! I' Q( ]
///往表格里面添加记录 1 }+ p# b8 s& V E: g& J$ ^. ^
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText);
, N. m- ^" g' s3 l+ N& @5 J: l ///将所有记录old字段的值加一
+ x, S) F8 ~+ H8 t# M m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText); 2 J1 I5 R# B# v- ~
///执行SQL统计命令得到包含记录条数的记录集 2 P, V5 G4 a- z6 C
m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
W; A6 |, a$ `: k _variant_t vIndex = (long)0;
& m' [1 v) h/ f _variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量 & Q0 f$ b" _- m4 A3 D. E
m_pRecordset->Close();///关闭记录集
0 F7 J& ^ h7 Q* _ CString message; ! \# U0 ^4 b, g/ D1 A
message.Format("共有%d条记录",vCount.lVal); ( N. V" F- D: r/ ` R( }5 m
AfxMessageBox(message);///显示当前记录条数
, ^6 W7 X, k9 M8 {: f
! Y$ @% ~$ u( {0 \" k0 l3 L" ?# l9 C& c
(2)利用Command对象来执行SQL命令 # {( S. r" \# b. K4 w. h5 Q6 z2 |
_CommandPtr m_pCommand;
: B3 e4 H) t+ P0 G" _! E m_pCommand.CreateInstance("ADODB.Command"); 8 L; g* h4 |6 {# J) ]
_variant_t vNULL;
+ O! B, m X6 r8 c3 c$ c. k vNULL.vt = VT_ERROR;
& q+ B/ Q- o( O, L% V/ ] vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数 ( ]# g# I- g4 m% @
m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它 3 D) [- q h, c6 z3 D/ p2 x
m_pCommand->CommandText = "SELECT * FROM users";///命令字串 $ A7 [* o7 h/ R/ c; R- o
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集 ' F* e$ E& V2 g0 i1 |
+ L- b4 V! A5 z' Z4 h, d) N
在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
" L0 w- V* B. x) c5 \: x7 R5 f; ?* c4 B
' [# }& \; E) C9 S; ]- Y
(3)直接用Recordset对象进行查询取得记录集
f" V% r' w; ~5 Q4 f例如
& |9 {# t6 D7 W- d- H$ E1 \4 D( M' ?3 ]! V W' o2 L6 E
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
9 Y e, J4 \; p1 Z! G
9 k" ]* f' f; \3 l2 {$ YOpen方法的原型是这样的: & l4 r G8 v0 v" b9 V" j
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
& O0 } `0 Z$ E& q1 f5 X% E) d其中: + M# ^ \5 b [9 }& I* n# D
①Source是数据查询字符串
" x& V) {7 r* D% ]8 V1 S/ ~1 E②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
; {0 C3 z7 r5 h2 v- M$ M③CursorType光标类型,它可以是以下值之一,请看这个枚举结构:
' m# V8 m& v+ P* d, a# Qenum CursorTypeEnum 8 K* G+ i4 ~5 A8 h) v
{
* L5 H% n2 Z, C) badOpenUnspecified = -1,///不作特别指定 $ j) L! T/ b$ U7 u' U3 Z' v2 i" M
adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用 $ p$ E4 ^+ ~9 x c" l
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
$ D2 r0 z; |# X# Z% }adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
3 o0 \& ?* W6 n: [7 L+ J/ o" ]adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 8 t" l; h0 C* K- M9 D
}; 9 }# Z0 n6 I7 J2 {
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
2 ], G5 h8 V% I2 g- [enum LockTypeEnum 6 @( W4 m u0 B6 Z
{
" ?0 k6 m- H' [! [% g9 Y: R2 L$ _3 LadLockUnspecified = -1,///未指定 ; J- y6 F* E* d+ N& G7 C- T
adLockReadOnly = 1,///只读记录集 " V) I; |7 T4 U. O+ S% W: n
adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 ' i, |( M2 I7 r! ~; y, o, a
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作 6 D+ `5 o6 L: p! r
adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
0 q& o0 {* U2 `6 H9 P. C. C) f};
/ F8 t/ z. ~7 J0 ?⑤Options请参考本文中对Connection对象的Execute方法的介绍 ' \9 K0 N! ^# X# S# D) S, C
' s' l- {" A0 y4 ~7 J; `3 g& R, ?
# G/ P$ C$ _" X _3 D( j【5】记录集的遍历、更新 + M. H4 _3 b8 P+ C9 ?8 v
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
4 w2 U; h! ]0 ~以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。
3 n" d/ D9 X& c, W
- x+ A" n: U$ K# [. J9 n8 k2 d- A: x+ |0 |& X$ U, r6 j+ j8 O
_variant_t vUsername,vBirthday,vID,vOld;
# L& W7 [' _5 l; C c% t- o% M1 `_RecordsetPtr m_pRecordset; * ` p m* ~5 E2 z0 Q; }$ H, K I
m_pRecordset.CreateInstance("ADODB.Recordset"); 9 _% F' f0 G/ s/ X- @1 J! s
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
; Z! g0 N1 ]4 l3 P" D0 c3 twhile(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗?
6 R, y: v% u4 t3 \% k6 p w{
* n/ W, N( a( W- @) z TvID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
" l9 G& z" o( A! d) v& o; q9 F, p! VvUsername = m_pRecordset->GetCollect("username");///取得username字段的值 ) d: x6 k/ B4 Y; W( l. M) c" K
vOld = m_pRecordset->GetCollect("old");
! k! L9 f2 h$ H+ f2 |; T3 w, fvBirthday = m_pRecordset->GetCollect("birthday"); 3 w. r: I5 _$ T1 q4 r5 Y
///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
/ U& f# t! O& t2 \ g! d# _if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
. Z( m+ B4 K0 ^4 L, Q' l TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); : R4 g; V! o9 R+ G3 @
m_pRecordset->MoveNext();///移到下一条记录
8 `9 ]1 Z! z" Y9 r1 V; x6 r}
# R5 o( i3 u* y5 ~3 H) r) T" @7 cm_pRecordset->MoveFirst();///移到首条记录 1 n& u A5 h5 k: L4 B
m_pRecordset->Delete(adAffectCurrent);///删除当前记录 ; Z; l* b6 Z- }" t5 ]$ ]2 C
///添加三条新记录并赋值 ! E' o$ z- A+ C1 X
for(int i=0;i<3;i++)
% g4 _& q$ ?3 M0 M6 e4 `{
, {* ?7 X2 A$ v9 ]m_pRecordset->AddNew();///添加新记录
: D+ V1 |7 `1 z% um_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
: r- n( p- {9 Um_pRecordset->PutCollect("username",_variant_t("叶利钦")); " }- ?4 e3 z3 T
m_pRecordset->PutCollect("old",_variant_t((long)71)); $ a, J' k" V6 _& G: e
m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15")); / q; x) Y6 j: @+ X* E3 z' \
} # b$ g; o" z s* l0 o% e8 e t4 R
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处
# s& _: g2 _# Pm_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄 ) N0 y" g8 F: P% \7 ~5 d
m_pRecordset->Update();///保存到库中
! e/ O- X* e2 T/ J t8 V% u, U/ [( K3 m6 |" E, F$ _
【6】关闭记录集与连接 1 y0 O- J- I `) R
记录集或连接都可以用Close方法来关闭
0 O0 n9 V: ?8 |+ W8 X" @ m_pRecordset->Close();///关闭记录集
5 A0 T, c7 _' i( c1 R& | m_pConnection->Close();///关闭连接
4 M! b# @7 h0 w0 |$ u
4 V) b' Q" M2 K9 D至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
& _4 M0 i' v4 ] y& G点这里下载示例代码 9 C) u/ Y8 V6 \: {/ P/ W; g6 n
( Y" J' {# y" k! C! k后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|