|
|
一、ADO简介 X. s6 ^7 ^$ x+ |( [/ |, |) [
ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。 $ j2 m: y& J c4 F* {9 Y5 Y9 @0 F
本文示例代码 1 m9 U! r/ ~/ ] Y7 u# Q
, a0 G& Q# W/ J/ }+ @二、基本流程
: T6 Y- K2 s1 D3 I+ [9 l4 g万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!
# e! K0 h" M# _5 ? y" N+ M(1)初始化COM库,引入ADO库定义文件
- @) b- q/ \( n0 Q* |8 u2 [(2)用Connection对象连接数据库 5 a" f. u& P7 x" V
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。
' Q( s% h+ }' [( h6 y# D, q(4)使用完毕后关闭连接释放对象。 3 J- y) K+ {% u- R% X0 `( l; ~
. z, n$ y) U5 m6 M; w
准备工作: . n1 k" Q. K6 t1 p% U+ \
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
; a+ k" ]8 B' L9 j% f1 b# i下面我们将详细介绍上述步骤并给出相关代码。 - _3 {* _4 y2 b$ T6 d- \" X
【1】COM库的初始化
8 }3 N( I R% R7 [- U, X* `- L我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
% T4 l' l9 Q) `7 ?$ J
& k5 ?4 f/ W, S5 Y. A, c. q; C; i v/ h1 L3 U6 Z
BOOL CADOTest1App::InitInstance()
B* `$ c5 G$ d {
+ D- J: n" q$ ^5 k) z AfxOleInit();
4 E3 C% }) ]" l% S ...... % V0 d ~0 E4 W; [2 T
0 ~& \7 g" ?' Y" L【2】用#import指令引入ADO类型库 ; z% `0 R' Z% F: ]. {9 _7 Q: P
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) ! a, c* B3 o4 N2 U) h/ L
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") ' M& f# I' \" @
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
4 o( w) P' M" A- U$ M2 c
; } C* Y# a6 G% x/ M/ Q几点说明: 6 a3 V. D1 U8 X* n$ V
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
; U/ D u" Y7 d9 P- v. Y7 m- n(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 8 R, a5 X# N( W
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned % j0 {% v1 |9 X' x. N
+ i0 y; x) R G% {, J; Y
【3】创建Connection对象并连接数据库 3 F( p/ A: l$ ~& I* i! ~9 b, }/ `8 i
首先我们需要添加一个指向Connection对象的指针:
! K% T. H' E. u" ?8 G* K q4 J_ConnectionPtr m_pConnection; 6 Q% p4 _) g; J8 V- D
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。
4 V7 N2 i; S7 b" I% J/ Q
3 S7 x' _ b* U; g) v% |6 e; q5 N) t$ O5 R% m% o0 R
BOOL CADOTest1Dlg::OnInitDialog() 3 a: c& b" |4 a0 G l
{
+ }" v8 V: v5 i) p CDialog::OnInitDialog();
5 B: U+ e4 {7 _ HRESULT hr;
3 }! S4 M5 i* M9 o$ P try ) N% d6 d: Q. r6 Q
{ : W0 N9 F& m* }6 Q, V
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象 ; I0 Z" f9 E) n Z+ y A
if(SUCCEEDED(hr)) 6 `/ T5 G! W' ?6 a) M- f
{ + H# z* y& E: [
hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库
! X6 n9 D6 d8 {6 c# S ///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; } 8 m/ c& Q. i) O0 w, e
} 1 h0 T' \3 u) `, X$ l( q9 Y
catch(_com_error e)///捕捉异常 ; [( C7 _8 q& ?. q& i% H; N0 {3 O
{
; h& Q6 u# K5 N$ m* b: v3 W CString errormessage;
3 W6 B3 }' Z- A3 O8 `; a4 D( K errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); g* ^0 b, T" ~( v- u1 R2 S
AfxMessageBox(errormessage);///显示错误信息 & A' r. d/ N% @2 I' o) C2 K
}
+ j% ^( Y7 {; u
# A% T3 E2 A9 V e& r1 F. f在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
; r0 X! G) Z( L9 f* l& BHRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
6 }/ _$ ?# Z+ Y+ ?ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
5 r, Q$ E% p [7 U/ O qOptions可以是如下几个常量:
5 P) j0 g9 D/ t- MadModeUnknown:缺省。当前的许可权未设置 " D4 o% m6 ^8 ~" l; f7 H2 |- Q
adModeRead:只读 2 n( B8 |# I8 u2 j# s
adModeWrite:只写
' C4 E5 k9 x2 O6 V/ TadModeReadWrite:可以读写
9 l0 F, |% S& [+ \9 MadModeShareDenyRead:阻止其它Connection对象以读权限打开连接 ' M1 y% e" p9 X: Q6 Z+ a5 x' y
adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接 2 y: [% K! g, k- a% y1 k/ B/ Q
adModeShareExclusive:阻止其它Connection对象打开连接 # F$ z* k3 s; ]- k! m
adModeShareDenyNone:允许其它程序或对象以任何权限建立连接 x7 |6 A1 Y8 m
, r# h7 g) u% q- s" Z7 ]/ w- B- m我们给出一些常用的连接方式供大家参考: / r# f# b' z Y% x8 x
(1)通过JET数据库引擎对ACCESS2000数据库的连接 7 y/ m1 z; w5 o7 Z7 Y& k; N% a/ x
0 D/ u1 J; P/ S* f
m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown); ) p: `& r7 a, U' b0 z2 k2 b6 D' P
2 ]( Z& u# s# k(2)通过DSN数据源对任何支持ODBC的数据库进行连接: * b, m' ?6 a. s8 a" X
m_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); ) @3 C1 Z' ^2 H- t# \
: t- d; k* `7 W/ L8 ~7 b8 v6 T(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown); + m% l+ E, Z, Y8 b4 z
# ]* }- \+ h) w7 n( A& }其中Server是SQL服务器的名称,DATABASE是库的名称 2 q8 f7 Y% C p) J' \/ q+ v2 W* x8 |
5 n+ }! b3 `7 d$ _7 P* ^Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State 6 K0 d6 s( ]# T. u" f* m( r+ h5 N v
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 ; ^. m" r" ?" N9 v! C7 R5 d
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
8 s$ g( M2 N$ H- h& I$ O
7 u6 ^9 v/ l8 D3 r* m& z$ T' b. b& v9 c
State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如: & b9 v( {8 }2 w+ S
if(m_pConnection->State) 5 z9 Y- g2 H4 g( F+ F/ W8 v2 i: U
m_pConnection->Close(); ///如果已经打开了连接则关闭它
! T2 r7 P L: V, x$ D: _" J8 w+ { R5 S- R; Z0 g2 Z
" F6 H* l1 l3 j) i8 |+ [" [+ B6 ^+ M
【4】执行SQL命令并取得结果记录集
2 C: h- X& B& y$ |. _为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
1 }* t# `3 K0 z2 E并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
9 k# L6 a) C' K4 {1 P% NSQL命令的执行可以采用多种形式,下面我们一进行阐述。 0 w# `+ A/ y5 o" l( J
$ O/ @) o3 [4 Z
(1)利用Connection对象的Execute方法执行SQL命令 ; I1 `+ B$ y) ~3 L" y; |
Execute方法的原型如下所示: , O8 |- W: O* Y% P
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
7 h& R$ {2 {& Q2 V" i5 `0 j. xadCmdText:表明CommandText是文本命令 ! E+ r; y o: D$ ]$ A2 E
adCmdTable:表明CommandText是一个表名
8 f: U2 k& `* d( \adCmdProc:表明CommandText是一个存储过程 4 ?- D# U7 |7 N& F. H
adCmdUnknown:未知 3 z9 d% C/ R/ H9 h8 v9 m- p, J
v% _: `6 T) Z3 I! U1 iExecute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected; ]" D5 F7 G: z; o- A
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday
+ b4 M/ O ^ b# z. v$ [ m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); 4 Z* b7 ?4 F& y$ D7 Z
///往表格里面添加记录 6 N9 O+ V. q; j4 X* Q- q9 `
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText); ( X9 X' ]6 C; r
///将所有记录old字段的值加一 ; z% n3 a Y& R0 V
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText); ( t1 f6 E( [$ h Y; J1 S1 f
///执行SQL统计命令得到包含记录条数的记录集
4 @+ m! q; V! |. ^9 K m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText); 9 Z3 J, K9 ~: c- K
_variant_t vIndex = (long)0; * U- {. f) ~6 p6 j' Q
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量 3 J3 F. v, s! f
m_pRecordset->Close();///关闭记录集
; {+ {6 w' q; ^! x \4 H CString message;
$ a `/ T* ]$ z6 ^ message.Format("共有%d条记录",vCount.lVal); , f$ O; m1 y& d( C
AfxMessageBox(message);///显示当前记录条数 " y, H$ n: m5 C# X S
$ P2 C! \3 I0 o( W8 ]% K: @/ e9 s+ _- @3 j8 P1 d8 O
(2)利用Command对象来执行SQL命令 $ n" J' h# [5 x
_CommandPtr m_pCommand; 6 y7 O* ^9 E$ f, P+ J/ t
m_pCommand.CreateInstance("ADODB.Command");
. U8 r9 G6 Z/ N& G" p' `( H5 e _variant_t vNULL;
4 z4 V- y+ V: b$ d% V" e9 c vNULL.vt = VT_ERROR; $ D' R9 q% x, H) K
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
7 t+ m3 D, p; ^1 w( C7 U* m9 ] m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它 * J2 Y7 |4 c/ p/ B% e
m_pCommand->CommandText = "SELECT * FROM users";///命令字串
1 Y. ~8 ?5 p8 c# S m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
# z8 A8 O+ d0 V" G# t! p( N8 b# ?% N! d2 ]
在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
- T+ W# E0 W* a
% [. n# Z6 K" ]' r* M% j) J) r; |4 h2 |0 G5 s+ R2 U v
(3)直接用Recordset对象进行查询取得记录集 7 c% w4 ^# g/ k. o* g
例如
* j: v/ d+ v! T# t3 k) ?4 J1 C) o0 F$ P9 u% y% |( [$ r
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); 0 e& T( `, j9 r7 P; ]0 B) m* }
& u, S+ Z# `6 s# }3 K
Open方法的原型是这样的: 0 ~2 D8 T0 g# M' _) ^& @! m
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ) 0 g& z; I7 ?+ B
其中: 2 t: G- b$ F; [: j$ w% W
①Source是数据查询字符串
( f& x+ e# k, s& l' P8 z②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象) % ~6 F; @0 o: M6 q1 f
③CursorType光标类型,它可以是以下值之一,请看这个枚举结构:
" I/ h5 G ]; e1 w2 ^1 S$ L4 n# Fenum CursorTypeEnum * t; |: g2 ?' a2 }
{ , G& b, h, ?% R" o. W
adOpenUnspecified = -1,///不作特别指定 & P2 r" u3 b3 U
adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用 0 Y5 l+ l- m% Q. S4 C( X: l* |; n! n+ P
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
: b6 q7 h4 o1 L9 oadOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
V! H9 o; B# A( M' {. ladOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 8 N8 r7 T0 H; w0 M
}; - Z6 a3 o/ H9 E6 g( v& m
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
- v" L$ ~5 I, w5 Q9 C+ p/ ~( ~6 renum LockTypeEnum ^. f2 x6 p+ f& u- \/ r
{
% i& v# i8 y" e+ s( YadLockUnspecified = -1,///未指定 1 Q& k; v! t3 [+ n' r
adLockReadOnly = 1,///只读记录集 ) S) g8 X' `+ o+ _0 S9 Y3 B; Q' y" e) Z
adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 % W) T2 f. x" \3 ]9 q. P5 P$ N( S1 \3 L
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作 ( ?1 V6 E* R* ?+ P' v6 U q
adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。 7 I* \0 H$ P5 C
}; - H! x! w. @+ \3 N* Q1 Q( M) ?
⑤Options请参考本文中对Connection对象的Execute方法的介绍 D2 O% o- Y+ k' P0 k
) x4 w0 D& m S6 j3 s* k
( d/ F/ k' z, r+ W【5】记录集的遍历、更新 % u7 p* `. D$ R# `8 }, `
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday 2 ?) k( T: P( \1 Z, z0 w4 ^' ~
以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。 & W. f( ~5 Z0 Z1 J* V' y$ A
" }8 x" J# a8 V1 d: C9 _
Y) b. J2 T' ]4 G* U% }_variant_t vUsername,vBirthday,vID,vOld;
2 Q6 x D( I; a3 J! g1 a& X. s_RecordsetPtr m_pRecordset; ' I" J# b7 v" v; v( X
m_pRecordset.CreateInstance("ADODB.Recordset"); + }3 i2 {% g+ A$ J3 z, f9 u
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
: K& A0 Y$ L1 ~4 V lwhile(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗?
3 f$ w8 t+ l$ w6 T; l; t{ $ Y8 R& }6 t: P2 P6 @
vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
! G7 }& U2 x* l4 j# g! f3 XvUsername = m_pRecordset->GetCollect("username");///取得username字段的值 - M+ C% L9 i# m* ?6 v8 T; k: Q! C
vOld = m_pRecordset->GetCollect("old");
# e* i; L, Y& EvBirthday = m_pRecordset->GetCollect("birthday");
! ]+ G4 Z- M" B7 u7 S$ N* Q///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
& y* n3 W1 d6 {if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL) " O: w/ `, i! t8 r" [3 J* l# d
TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); . _% D& R% G$ x( ]7 A
m_pRecordset->MoveNext();///移到下一条记录 8 }1 u7 s+ ~ a% o% U
} 2 ~1 r% y2 W( y0 L& I5 _
m_pRecordset->MoveFirst();///移到首条记录
- w* r- r$ V: Wm_pRecordset->Delete(adAffectCurrent);///删除当前记录 " v" |" h5 l5 ?0 w: q/ M
///添加三条新记录并赋值 ' U9 R1 |* I7 z( ]
for(int i=0;i<3;i++) - J' r1 x6 p o3 V: I
{
7 E9 n* H0 H; Em_pRecordset->AddNew();///添加新记录
) E2 G, h. k$ [* ?& mm_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
& U4 g0 |1 `4 b2 \5 \* zm_pRecordset->PutCollect("username",_variant_t("叶利钦"));
3 } T; l4 i- x0 @m_pRecordset->PutCollect("old",_variant_t((long)71)); , M \/ p$ R( c7 y l; |( `
m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
- p* O4 O" s( Z+ y. G3 u} ; R8 y8 G4 I4 S% w, B, P" n$ f/ u
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处 3 p2 O& U( K( g% V
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄 $ R0 S$ B$ x, @# E, \5 {
m_pRecordset->Update();///保存到库中 : v6 S- s0 Y" [9 q, ~8 a$ I
1 u$ Q& z* M* U9 C" j【6】关闭记录集与连接 2 p" i" S* X4 H6 ^$ y! e. N& d
记录集或连接都可以用Close方法来关闭
V( y( C, e, H, {. O$ ~ m_pRecordset->Close();///关闭记录集
9 ?+ H2 d' [5 d( k8 y+ i m_pConnection->Close();///关闭连接
( k6 M a& M7 G5 K# g: }+ w2 a7 c/ U; V: C, g: x
至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
1 s% F; g* D) S4 A& }& V点这里下载示例代码
7 u) k& r" D2 x3 w" U
: `4 h; Y! Q9 }4 k8 R% j, z# T# R后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|