|
|
一、ADO简介 ' D' S, o8 {6 S
ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。
! W6 _* z% s1 V' S本文示例代码 & R" Z! ~( L, c1 d
; F& j# ]( A' ], w& f7 |7 r
二、基本流程
7 K5 `/ z# I0 c }$ X8 C万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!
( S! O8 ?5 X- s5 ?2 \' m(1)初始化COM库,引入ADO库定义文件
1 ?* M% x" u5 L* y5 H6 f(2)用Connection对象连接数据库
, _! @/ y! w+ |+ k1 S(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 6 q& ]- @; _) O: ` N8 ~$ K- C
(4)使用完毕后关闭连接释放对象。 - I4 x& f- h q* J% R: V8 ?
, G1 c5 i7 \. `6 v F' ]
准备工作: ) ~" ]/ y7 W. v/ \
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。 4 g( G4 _( Y6 M+ ~1 {# p8 r
下面我们将详细介绍上述步骤并给出相关代码。 * Q' t& w& E. W6 K2 K
【1】COM库的初始化 4 N# ^2 c7 e' _/ Z
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码: 1 ^0 A2 |" @( Y- P6 \
6 ^+ c$ o: Y% Y" \. ]% {' N
" D' V7 P) ^: _' NBOOL CADOTest1App::InitInstance() 7 M, k! \& O! U: g4 Z
{
U b2 ]" u$ X! n, J' r6 C AfxOleInit();
, a6 o$ T" [6 d3 z& V& C% | ......
: W2 K( Q! Z! W0 G2 @
: k8 l6 I2 Y- d6 U4 y2 d4 k: [【2】用#import指令引入ADO类型库 * K' e9 r4 ~' D( B) v
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) 9 n& Y7 L1 O0 q l) I
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") T& F! z1 x9 C0 h
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。 6 z+ E1 e9 o' ^
* \6 u; |" j+ I几点说明:
# ^6 L3 ]0 j0 J+ D(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
- t6 j6 G- o! P; o; ^5 y) G(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 0 m6 g9 S" X+ M5 L/ N4 x( K8 A" {
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned + }8 z& }; h/ e
" ]. d* k- B$ f7 a5 z& x6 |
【3】创建Connection对象并连接数据库 \) K% v9 y* t5 V+ P; W: q9 C
首先我们需要添加一个指向Connection对象的指针: 1 I8 T; `6 X0 o5 v9 K
_ConnectionPtr m_pConnection; " R T5 K. i1 O8 V
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。
3 k( o) z! L& z5 z& |% o
, y$ U% T6 O1 T# z* `5 o1 Q. z& I1 H6 S9 m0 E3 p
BOOL CADOTest1Dlg::OnInitDialog()
3 C, o, L! f, j6 V1 d% @: \ { 4 s0 Y. [/ g$ e' m) V
CDialog::OnInitDialog();
3 B" ?& D. H& y# [, Y HRESULT hr;
3 j, k7 S+ `% E4 h/ G; R try
0 i. e2 K* D! D: p! |: [1 [- m {
& A$ `7 w& v6 \+ w1 F hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象 5 q2 L% g: F- h; g
if(SUCCEEDED(hr))
' e9 f1 M5 p2 u {
- F, U" W& r+ p5 B7 S hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 # n7 g1 r9 K: f. j# l# W( M3 ?
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; } 7 U' Q' t/ O+ x. k
}
- E( Q4 Q2 a, }! ~$ g catch(_com_error e)///捕捉异常
$ S( x9 f' n) ~7 f0 \ { * \; o& N$ q1 i& z5 ?8 m
CString errormessage;
3 P" @5 ^; [" s0 _$ L' [ Y errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage());
, J( z/ }& r1 @ s& y; t- Z+ F AfxMessageBox(errormessage);///显示错误信息
* V5 Q! l6 X6 n8 U, y } 2 M6 ?$ ?7 w1 y6 X! f- T/ d
& `( m! S* h+ i' Z
在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
' {. @5 g" T/ K1 l: C# w0 N/ s: c& GHRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
5 y+ o6 L, l, e2 H( n0 D1 o" l% wConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
9 t+ f8 R0 D q; l: p. u0 M4 o. r, wOptions可以是如下几个常量: % j O- q9 }1 M% ]' O
adModeUnknown:缺省。当前的许可权未设置 $ j2 F9 x# q# t# _3 D5 y2 a
adModeRead:只读 & @' k/ a( r2 W$ c: e/ X! C( t
adModeWrite:只写
% K# C! i$ O( F% [adModeReadWrite:可以读写
" E8 b0 I7 x, s! padModeShareDenyRead:阻止其它Connection对象以读权限打开连接
2 r- a+ @. a: c; A; _. o; qadModeShareDenyWrite:阻止其它Connection对象以写权限打开连接 * t7 `0 _. ]9 F" i/ g% @5 G
adModeShareExclusive:阻止其它Connection对象打开连接 % R' n2 O) @! ^: P/ n2 V
adModeShareDenyNone:允许其它程序或对象以任何权限建立连接
# b7 j" ~" m9 O$ o$ W. s0 o6 h! t( z6 o1 H
我们给出一些常用的连接方式供大家参考: / j J2 W2 t$ T3 }2 S, _/ u* j9 x0 J
(1)通过JET数据库引擎对ACCESS2000数据库的连接
% |) f) ~5 `) P# p# Q6 j# R8 K6 n4 h! q. }4 d
m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown); 7 E4 d" ~2 i6 ]3 D# [, F: c
f3 s1 x( j4 A* v0 X) w! r# X+ u
(2)通过DSN数据源对任何支持ODBC的数据库进行连接:
. M S8 h G3 W# _2 ]. Hm_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); 9 x' ~/ j! p/ W
3 f( {( j! Q4 X(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown); , s: y" ]* j. O. D% [+ @0 {8 h
4 _+ I, I p" N3 l# P- x其中Server是SQL服务器的名称,DATABASE是库的名称 # I# o k: D% n% p6 n- R
) h! R4 w& [: r7 D/ f% z( G& D
Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State / p t2 U6 y/ L; R% `! j
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 ! r- |' g. e4 h$ B7 t+ C0 b' g
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown); 3 Z! u U, V8 M# H
# o9 q# J4 r7 ^7 s+ M8 y5 |
$ i, i* g' @' t% }; NState属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
; K: ^ J. d* C. T/ ?4 U$ J2 fif(m_pConnection->State) ; U& D, Z% ?8 a6 z5 S/ B) a3 a( ]
m_pConnection->Close(); ///如果已经打开了连接则关闭它 ( N' h' L. ^( T4 x
6 M$ C: i- ^0 R* G. i" Y# ?0 S" j) y! x; E8 {) X4 E
【4】执行SQL命令并取得结果记录集
% z! ], F6 _0 y3 K4 ?4 d3 r为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
# H) w' [+ u7 P4 e& }6 O3 N/ e4 ~- l并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset"); 5 G7 r7 Z: T( e; a7 }
SQL命令的执行可以采用多种形式,下面我们一进行阐述。
% n% y) T. A W
% Y! l7 ^* X, j$ r# b(1)利用Connection对象的Execute方法执行SQL命令
, m# a! a2 U4 fExecute方法的原型如下所示:
" T8 k$ R i K4 z' s$ t" Z4 h_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一: + H6 L" ?) h% S, s- h) W
adCmdText:表明CommandText是文本命令
$ W; r9 k1 w4 V/ d( `' z8 NadCmdTable:表明CommandText是一个表名 : z: U9 \7 B2 W& E+ f1 s* c$ Q
adCmdProc:表明CommandText是一个存储过程 5 x! x7 o& l$ R8 ]
adCmdUnknown:未知 $ ^" L- l9 g+ ^5 U- S
( g i" H. h+ M% L
Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected; 8 U1 W w: p4 h, i) }/ X
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday
- \4 Z1 c" _$ Q4 m* G0 U5 q m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText);
# t* \0 I8 t* H/ k. v8 K f ///往表格里面添加记录 * h+ W3 b! N# v% K5 S
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText);
/ n+ r: @1 U+ s+ I9 U4 b1 E: \2 P ///将所有记录old字段的值加一 ) |6 }: V$ V/ W# B- Z
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText);
* `+ s" R% _" q ///执行SQL统计命令得到包含记录条数的记录集 : y8 c. d! R4 O. q; ~' p
m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText); 2 q( m% C: }# x0 f
_variant_t vIndex = (long)0;
: Y8 y. i" O( N W% F _variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量 " r. o( ?! M# ? z
m_pRecordset->Close();///关闭记录集 ( V/ ]( d6 }. k3 b, q
CString message;
; `; o, d( K% J) }, w. m0 L message.Format("共有%d条记录",vCount.lVal);
6 q5 d* u) k1 J: A: x AfxMessageBox(message);///显示当前记录条数
$ L3 c p; r1 J; p% ?* E/ b/ s1 ^( D6 r# l
0 c$ u( G2 }: u" D# ?(2)利用Command对象来执行SQL命令
[) T( t; u" i: I% e _CommandPtr m_pCommand; 4 R0 R, z8 k; M' A/ Z8 p. s! R
m_pCommand.CreateInstance("ADODB.Command");
) u% l* _9 N3 i8 ]$ X' \ _variant_t vNULL; . k! C# D+ `# ~3 E, _6 `$ q
vNULL.vt = VT_ERROR;
5 O8 a+ e. f4 G" B! u8 [8 ~ vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
9 K! \" n! `3 e5 ^ m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它 , m1 H5 H# j) }$ o7 B" t
m_pCommand->CommandText = "SELECT * FROM users";///命令字串
& b S0 j% {5 J3 {1 N+ t m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
% H' l0 H# C7 {& u! }# C, b
- Q; W3 d3 m* g# @; J9 u在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
2 v, s. N# X& d0 c! A. H3 _; ~
# s/ Q! Z1 R# M1 j z* O3 ?
6 U' d# \) c' |+ Q2 S; J(3)直接用Recordset对象进行查询取得记录集 9 ^4 z; l( J. \3 \- y
例如
: R' {) b* E' r4 E* R
0 Y8 `! h( T; i2 \: \% S, L m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); % V- Y% v! D k) P; j& X& r1 {
r" Z1 }3 M3 N+ `
Open方法的原型是这样的:
# C7 O7 l2 o! F8 BHRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
1 C, {2 t0 X, R5 U: N8 D其中:
5 ~7 D l' r$ w3 x3 G. Y①Source是数据查询字符串 ' u e& O7 T4 a' V" Z ]) i" S
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
1 y; ^" {/ ], W5 j* s* v* p③CursorType光标类型,它可以是以下值之一,请看这个枚举结构:
- H* \' S) x- f5 G6 k* Fenum CursorTypeEnum 5 [& V" \, E) p' J t( w b4 k
{
$ a+ r7 }- k3 |3 G7 o# XadOpenUnspecified = -1,///不作特别指定 ( H5 j4 f6 G. X
adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用 6 D/ j! X8 _8 @4 z/ B' ?
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
' b7 C! h4 `: s& r; cadOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
' k6 |4 b) S5 h& T$ LadOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。
- B) X! a: ` f8 p4 e: i8 @}; 7 \) i% D! w1 k8 h; A
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构: " }) |9 u% R) o6 @
enum LockTypeEnum
. V6 b a' ]# d7 ^3 N{
; }) N/ S+ z9 o! HadLockUnspecified = -1,///未指定 ) b2 X) P3 X$ B( R7 a6 S
adLockReadOnly = 1,///只读记录集
( x) N; D+ W @$ ]9 {: m$ {2 N, |adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
& }1 J5 `& `3 O# g, [adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
4 K* q9 a% N: GadLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
/ v* N' f5 q/ W* ~}; ; h% `' I+ m% d6 b6 X8 [# A( R5 s
⑤Options请参考本文中对Connection对象的Execute方法的介绍 6 W; o* Q; r n
6 M! L8 D; J7 O* R. R# I
/ ?$ _' w* J; {$ c; q
【5】记录集的遍历、更新 ; e9 ~) z7 f/ Z3 C9 a& e
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
6 j# ^7 X1 W( t/ h6 s以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。 8 @+ u! P i, m, x
0 h/ q( \1 S' h1 ~# H0 b/ M+ b4 {6 ~- X
_variant_t vUsername,vBirthday,vID,vOld;
8 ~' J! [8 _9 m6 i_RecordsetPtr m_pRecordset;
( P- S4 E3 J( o# o3 f$ O7 U, `m_pRecordset.CreateInstance("ADODB.Recordset");
! s8 W& H: p& I( i* L0 @9 ^3 T8 dm_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
$ [: T- {- X; O: e! dwhile(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗? W# M$ R% U$ }
{
' U: {; G. r. z& Z L$ WvID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
8 _/ z0 t; t# z5 E# E( Q$ C$ svUsername = m_pRecordset->GetCollect("username");///取得username字段的值 2 n4 R$ E, L3 R$ U
vOld = m_pRecordset->GetCollect("old"); d( L) y- m9 `7 w6 ]/ @! k1 @
vBirthday = m_pRecordset->GetCollect("birthday");
6 R# X B- e0 s1 i5 M///在DEBUG方式下的OUTPUT窗口输出记录集中的记录 ! x: n( k9 J3 d9 e' y$ A, y
if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
# @4 I: t; L9 `+ ? TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday);
7 g0 t2 i% Q2 lm_pRecordset->MoveNext();///移到下一条记录
: V$ i/ ^1 y" x- B* M9 e} # P& m, y# t( i7 O2 q! l" w
m_pRecordset->MoveFirst();///移到首条记录
% \ Q3 |5 N8 C+ bm_pRecordset->Delete(adAffectCurrent);///删除当前记录
& z2 `: ~; B U9 Q* L///添加三条新记录并赋值 X1 O; Q6 r. m4 J8 Z2 ~7 [6 n
for(int i=0;i<3;i++)
; n8 `0 \/ b% Q# t- i% \( Q{ 9 s+ L; a" u5 e+ ?; }1 E0 o! `0 D- l
m_pRecordset->AddNew();///添加新记录
& w3 O! ^; b% M9 N. sm_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
! b( D6 g$ Y& r8 `' m- Qm_pRecordset->PutCollect("username",_variant_t("叶利钦")); 4 W' w9 F# F3 S5 v) n
m_pRecordset->PutCollect("old",_variant_t((long)71));
% u5 o r! e; }1 D! wm_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
/ r8 b6 I! g' m+ q5 i} + w5 ~1 t* K2 s4 E# A O* ]/ i
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处
: f! G" E& M7 e# I- [+ M& tm_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄 ) D( t, c }9 u ^6 C# w, }
m_pRecordset->Update();///保存到库中
: c+ ?. v0 d7 b6 ~" Y% F3 z E3 c/ Y6 ]# R! Z. L5 _; p
【6】关闭记录集与连接
' C6 H9 c4 @ J0 Y; }+ d; }记录集或连接都可以用Close方法来关闭
* V7 \: |) Y4 h1 C& u R- ` m_pRecordset->Close();///关闭记录集 / k' V7 w2 B H7 X0 r! \' a( G6 f
m_pConnection->Close();///关闭连接
' D! l: A2 ^4 B* v" F
1 x9 ~2 a3 D8 B) r& u& |- A8 ^至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。 ' h# P8 |2 N8 G+ k) T
点这里下载示例代码 3 h3 ]+ j; y1 d8 h8 W( M* A* |
x5 _% b/ }( ?& n
后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|