|
|
一、ADO简介
. O. I( k$ x* B1 U$ xADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。 ! w `+ k" {' r2 X% e
本文示例代码
& u& A- {' Q, |. c+ U2 k. \# Z# o+ u. s9 D+ K
二、基本流程 - O* {! s- f: ^8 {6 J
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!
: M2 B! w' S7 n(1)初始化COM库,引入ADO库定义文件 " l: h6 \" ^. Z+ X! M6 e
(2)用Connection对象连接数据库 6 s1 {4 S; E8 b7 x" d
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。
. K# |7 u/ N2 I+ `7 }1 {(4)使用完毕后关闭连接释放对象。
5 Y+ {' W2 ]$ W% ]+ a0 k
1 C/ H( v" z/ {$ N$ p, E0 I/ i' [准备工作: $ Y: P, \$ q7 ]% T
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
+ O7 p' t. ^# F, Y$ [下面我们将详细介绍上述步骤并给出相关代码。 ; Y: m2 E7 S8 c5 `
【1】COM库的初始化 / B0 k: K& v& x3 d6 R
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
; u! Q' \9 m/ ]' X5 b' y0 \8 M5 M! K6 ]& h% O- y
8 ^1 ^5 e% P8 r1 Z1 I: r
BOOL CADOTest1App::InitInstance() ; s/ B! b& c$ X5 c9 s2 q1 w
{ + ^; a$ p8 q1 @7 X1 k- |5 s. Z
AfxOleInit(); ) G1 @: e2 b0 F% `+ [4 L8 x- |
...... ; j ]' c; z6 d7 k9 @
3 F/ Q0 p/ _9 j3 [" |5 D5 [# [& `+ @/ M【2】用#import指令引入ADO类型库
$ {5 ?" b# U2 v4 e9 U我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) : Y' J/ u I6 z& @7 u1 o
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") 7 d2 `- y% w c b- ~$ @0 S0 S
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。 8 K9 N6 l$ E( N
" N0 \( k4 S3 C7 V几点说明: # J2 F, V( Y5 V+ G! k% R: u" v3 h
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
/ t% z# q# O' P* ?" @0 ^ q* Y(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 " k6 Y6 G, U/ B2 L3 k- p+ E- b
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned + Z" b0 v" N: D- P6 a2 }" |' ?
% ]# p; a! c& S' W
【3】创建Connection对象并连接数据库 % A" p* y1 {& m0 X5 r9 @. \% P5 T4 N8 F
首先我们需要添加一个指向Connection对象的指针:
. k: g$ f) n3 k_ConnectionPtr m_pConnection; + g+ e$ ]( X5 ]1 a8 J, t
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。 * i r. k* N- H% \. w, @" e/ Z; N
0 j4 d8 ?# P, y0 ~1 g
* Z5 M0 [/ u q: z" m& K" Z. X }BOOL CADOTest1Dlg::OnInitDialog()
$ _) \# K- h2 K8 I" N( }) e+ {8 t {
/ y% n) v# Z" m3 u CDialog::OnInitDialog(); ' z) G# E# m* J* C8 c7 G2 i
HRESULT hr; 7 b" W( Q2 ] T+ o3 ~# |, [' i
try
* U4 P/ A2 G8 x {
$ p5 J6 L* R" l% j5 ?+ N& P hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象 5 k* {9 n- p3 Y4 j1 x i% R0 _
if(SUCCEEDED(hr))
" N$ t* j4 Z9 _8 f! j4 l$ s8 o, H {
2 w8 h+ [& Y5 l; D8 f- t) z' Z% R6 P7 B hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 . I! c" |* s5 S* l8 ^) s
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; }
% L. W. a# C9 J$ Y }
( E( h* k3 B5 L- K catch(_com_error e)///捕捉异常
7 ]/ |! D% [8 H+ ]4 I/ v# I6 Q8 `+ N4 N {
- U8 a! ^, i- B$ ]0 o+ Q CString errormessage; " H; b" K+ t1 y; u! M
errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage());
. v( B! Z2 W9 r AfxMessageBox(errormessage);///显示错误信息
5 Y' Q" P5 P$ ^- z7 |) U m }
6 x/ c& \* b' k7 {& i& {1 Z {- \4 \" m3 X" |0 g, b
在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型 1 E6 z2 Z+ `2 Q# J% F* C1 [2 S! [; Z
HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
& p8 D( J4 @8 A; h4 KConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
5 V4 l. t0 Y( G1 o4 W, WOptions可以是如下几个常量: 5 `: G/ B' r! j, B/ e9 q
adModeUnknown:缺省。当前的许可权未设置 + N9 }, u2 ?) y" } }0 F* [
adModeRead:只读
8 R8 y+ d2 R1 w5 _7 JadModeWrite:只写
0 q3 Y" T, r* b S; z. wadModeReadWrite:可以读写 ) g' ^: Z' a+ v% {6 \+ }
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
6 L7 S3 ^* A7 H" v, iadModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
4 D G B! t8 OadModeShareExclusive:阻止其它Connection对象打开连接 . s @; D* t$ I2 D
adModeShareDenyNone:允许其它程序或对象以任何权限建立连接 4 ^3 ^5 P" s0 t1 z8 o# h
8 G' ?: T/ e- Z我们给出一些常用的连接方式供大家参考: 5 H: C; C/ m9 F5 o0 s" I
(1)通过JET数据库引擎对ACCESS2000数据库的连接 - C q$ Z- t3 z! X. ]- |/ e) E- O
- U( X7 H2 p# R6 U) c. C' O+ W
m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);
3 o2 N3 \- g/ U4 r# I" H) R
0 y3 M2 Y& [) Y& [6 T(2)通过DSN数据源对任何支持ODBC的数据库进行连接: - o; W- D1 R3 A: d
m_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); ' _: ?. E G* K& `
3 h1 f( u! M, b0 }(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
: l: p! w5 S& q) _3 v( I# e. a* b
其中Server是SQL服务器的名称,DATABASE是库的名称 , Q, S' W# d% S* D- O
+ r! Y/ P# t5 H" @/ d
Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State
( w% @; k) j oConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒
6 i3 y) f" N8 ]4 A" f; [m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
2 `' ^9 j4 E" c+ g9 Y( T: B/ x
! O2 u7 V2 i" c% Y# k, w# w* A
0 f; W4 u3 O/ h& X# F( u& i+ M, R! DState属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
6 `$ N; m' v, _8 n( uif(m_pConnection->State) 3 o5 |. s* C9 j8 T, D
m_pConnection->Close(); ///如果已经打开了连接则关闭它
6 J2 `6 \1 d) M8 h! z
) H( f# j1 [7 [7 g" K
: |+ P: @, c( Q9 g' T, G5 ]【4】执行SQL命令并取得结果记录集 : q' Y5 p$ |6 C% J" \+ |( Z. q
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
% a Q$ A' b6 f2 M9 ?5 U, a并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
1 T( a5 J- \7 R% |5 W$ v/ M/ NSQL命令的执行可以采用多种形式,下面我们一进行阐述。
# B: ` ?* L9 ^5 s$ }
5 S0 q- d0 |; F) m; T, U(1)利用Connection对象的Execute方法执行SQL命令
) {$ W! G" J- B& hExecute方法的原型如下所示:
7 j- D! Y2 u6 K# {" ^_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一: 2 q5 H, H( u e# {. a8 T
adCmdText:表明CommandText是文本命令 ) X: |3 T- s3 K
adCmdTable:表明CommandText是一个表名 6 d6 ?3 c% e+ v3 t% e
adCmdProc:表明CommandText是一个存储过程 6 V: O; {, U2 R4 x3 n: m t
adCmdUnknown:未知
1 T4 G; J* n( F# I8 ?- v" _. W2 S: }0 O' }9 @
Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected;
. w2 v u4 T8 q6 w ///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday 1 V, G% i4 h/ K" D
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); % n4 T3 w9 h* M! x6 o
///往表格里面添加记录 2 A* d$ Y! y4 P% K l
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText);
, n# o3 d/ M _ ///将所有记录old字段的值加一 : D$ o' R4 {4 i. t* D, _ S
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText);
: s( U9 g E2 q4 b) S A ///执行SQL统计命令得到包含记录条数的记录集 ~% ?+ k) Z$ ^% s) X, v
m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
/ |$ s6 X4 w4 @% ]; v' k _variant_t vIndex = (long)0;
( k' y9 B5 S; H9 }; { _variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量 + X. Y* {8 V* i% R/ q/ B {
m_pRecordset->Close();///关闭记录集
7 c3 ]/ J8 N! b9 G; E3 z CString message;
# \( n. d! K- @8 |% z4 t- y3 x: c message.Format("共有%d条记录",vCount.lVal);
Y" k) T! v0 {$ b3 j( v% f2 v AfxMessageBox(message);///显示当前记录条数 9 }3 j4 _. K* T% |, h$ J$ c
: {6 b3 \! }/ a' A
, G6 G" a% e/ J3 G) S+ ~! E! P
(2)利用Command对象来执行SQL命令 . L5 x1 N% t) h; a: K, K1 O
_CommandPtr m_pCommand; ! L+ q3 F8 N6 S8 V$ K) [6 q( R
m_pCommand.CreateInstance("ADODB.Command");
5 M1 H- X* [: a# L6 n, O% q _variant_t vNULL;
7 N0 o" m5 D) I! B l" h* a8 B vNULL.vt = VT_ERROR;
5 c7 p; ^! r0 M/ u& X. N vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
$ d! \7 X" a4 N% m m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它
' M1 l7 g! t' n2 Q m_pCommand->CommandText = "SELECT * FROM users";///命令字串 : H7 P; i; u i6 v3 D& k) Y) t$ A. ]2 m5 R
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
! h5 _) Z3 l3 l6 r
2 G+ ]; `, M4 J) W8 j$ r在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。 7 g- a6 y# o6 l" R: |5 L
8 Q' q# e8 D5 Q6 V2 i- B0 n7 ~9 K7 P: g
(3)直接用Recordset对象进行查询取得记录集
3 Z- v0 ?3 L) G/ f: z2 s例如 1 ]4 O% L9 ~/ G+ W9 M
- J, z% Z3 i7 o2 N) C! G# S m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
1 [: e! k5 d) K) Z2 b7 n/ `1 c2 t4 K4 R+ ?7 a- e
Open方法的原型是这样的: & A9 @- G. B7 o
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
$ S4 q: s4 y( }6 @0 P+ ]其中:
6 |& ~5 V9 U! I. i, v8 c% d, c①Source是数据查询字符串 - F$ V" r. L+ v! G3 g0 G9 R
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象) 4 r, ~0 y0 a x9 v! P
③CursorType光标类型,它可以是以下值之一,请看这个枚举结构: , j9 _- a3 S& m! ]5 j. a
enum CursorTypeEnum - a3 ]$ `- S+ ]/ L$ x
{ " _- M v- M9 S+ h8 ~
adOpenUnspecified = -1,///不作特别指定
4 i* v" [/ V' ^2 b, R- jadOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用 - F9 h: J2 X) q1 x; a
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
7 K7 g; d: s. `3 z8 f& k: U. A. KadOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。 & \" c$ o+ A( @; d! I; x+ z
adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 $ [1 h! a/ z9 I/ Y( D- b
};
+ v% \9 S: v- G" P% R④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
8 c- H0 N9 J3 V" W0 h) t% Menum LockTypeEnum
' |) w+ Y5 {: E5 e8 u{ $ Y% W, I3 M0 O* v( p- O/ U# @. E' ]
adLockUnspecified = -1,///未指定 . K1 _3 p1 X6 I
adLockReadOnly = 1,///只读记录集
7 L: z& v2 s, dadLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 / p+ {' C! Y% F9 Y6 _
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作 8 {3 ~0 T) l7 {0 z6 C5 M/ j
adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
5 _! B' o! i6 t8 T; [, P0 p}; . U4 ~3 X. t& i6 Y
⑤Options请参考本文中对Connection对象的Execute方法的介绍
8 G2 R$ `8 ~. i; }
# ^9 c4 p2 q1 o- y7 X% @+ |
. Z0 O& ]8 P8 D【5】记录集的遍历、更新
* U5 \, G0 H3 n- f6 m9 i根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
; f5 Q+ P, a2 @, }以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。 - H/ W; h2 z& c a7 v% O! z
' u) @9 O$ @; w# V( h3 U# T
% ^ O; J1 a: D. f1 L+ q9 B_variant_t vUsername,vBirthday,vID,vOld;
( J5 U" ] h: J9 P" K4 P6 G" u_RecordsetPtr m_pRecordset; 6 V& w6 @5 D8 x9 E0 v. y5 G q7 l
m_pRecordset.CreateInstance("ADODB.Recordset"); # l# C) S6 t- k6 {+ R/ z6 g7 o
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
2 \7 N7 r* `) X7 o I- }3 |( Pwhile(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗?
9 K: P3 ]4 e; O0 c{
4 B# I) v: w( Y; X+ S7 @) zvID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行 6 q; @* ?2 V3 I1 W' l
vUsername = m_pRecordset->GetCollect("username");///取得username字段的值
$ h0 F. Y' Y/ ?+ MvOld = m_pRecordset->GetCollect("old");
. F0 M5 a# F1 zvBirthday = m_pRecordset->GetCollect("birthday");
# v( v. |) ^* L% f/ Q///在DEBUG方式下的OUTPUT窗口输出记录集中的记录 5 z$ {9 G a8 L& W
if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
; f5 q. `, e \ TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); ; m; b/ _. H: \1 r: T
m_pRecordset->MoveNext();///移到下一条记录
8 R( B0 _: F b, h; b}
" m2 O# { `+ i4 r0 k0 m3 b( Wm_pRecordset->MoveFirst();///移到首条记录 7 H* W# J3 S4 Y! i( `( u p$ ^8 _
m_pRecordset->Delete(adAffectCurrent);///删除当前记录 & u, j8 t* J* h0 n) A7 W5 `0 g
///添加三条新记录并赋值
' \. v9 r1 x$ Afor(int i=0;i<3;i++) 1 M* w K$ i7 }8 {1 O
{ ' B0 e% a! D8 Y1 G
m_pRecordset->AddNew();///添加新记录
" R' @2 ]3 L# B& `m_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
- B% M* C) O* I4 [( u" v! Bm_pRecordset->PutCollect("username",_variant_t("叶利钦")); # y2 n7 D+ L6 B; `9 M% T
m_pRecordset->PutCollect("old",_variant_t((long)71));
5 S2 z* h2 I% J0 U5 J6 Um_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
3 g8 ?/ J F5 E# x5 d) }* T& O( [} & }' N7 S; o2 b7 N8 J+ x
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处
, y5 ]$ H' ?! _7 M4 `; bm_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄
' r) H3 L/ Y4 l4 ]6 ]m_pRecordset->Update();///保存到库中 3 f) x" ~5 s. U$ J+ ?2 `5 S, Z' C
; m7 h1 _. b$ g3 I2 U【6】关闭记录集与连接 : ]' r1 M M+ @! ]) K
记录集或连接都可以用Close方法来关闭
: \ Y4 p" n% @3 o+ \; {- j m_pRecordset->Close();///关闭记录集 : R" A4 N4 N4 m8 F$ P
m_pConnection->Close();///关闭连接 3 ?9 }! L% _% Q7 p8 a2 Q
' ~& I% h6 `5 m% M
至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
9 N3 P' x/ h7 G' S6 W0 ?点这里下载示例代码 1 d4 E; B4 d' h( m2 U7 c
@& ]; k ]- z/ a
后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|