|
一、ADO简介
, p$ j( p/ Z/ MADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。
5 Q$ t* z! j M1 B* {- W6 n本文示例代码
2 p! j7 y+ q& I
2 M$ u) m0 L/ }* n( ]2 e二、基本流程 ) c3 S% B3 h, I# V% `. d1 c
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!
' Q- R! Q) `5 n7 N9 x+ j(1)初始化COM库,引入ADO库定义文件 . M$ \2 q- h k% E% B3 D
(2)用Connection对象连接数据库 + h- B. i$ r3 W: I
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 , O: o c/ z, n" ^, S: v h
(4)使用完毕后关闭连接释放对象。 * m2 P. d0 T7 e
4 L* ]% q. \8 x$ V准备工作: # B* t0 n1 f) E0 p8 m% e
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。 : s$ ?9 r; @/ L4 ]4 [. \& y% }
下面我们将详细介绍上述步骤并给出相关代码。
+ j4 [0 H. O& t【1】COM库的初始化 , z- }! y& c) @& Z7 }7 I6 U
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
! E0 w7 x8 P" e, a3 | `5 O U0 U! ?, g
2 A# _, ^7 h3 u2 H5 N, |
BOOL CADOTest1App::InitInstance()
* x+ V9 F" R% O8 G7 } {
# W/ U: W O! T4 ? AfxOleInit(); . V5 s9 o$ q8 B! e7 q" z0 c
...... 7 u7 V( O, C7 L4 U4 |4 M
+ H7 R2 X9 |# n
【2】用#import指令引入ADO类型库
. {. T. X+ p2 f我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到)
( q7 x. P, K6 d4 {; O#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
3 \4 F2 a' Z6 O Z0 ?; P3 T这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。 4 H; ~1 N( ~$ V7 r9 t0 u/ v
* ^( l3 v7 I: Z2 F1 D
几点说明: 3 Z# U5 F6 B! r6 q, w: N$ i5 H: e# E
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
# G4 ^7 `# c0 i" J( d4 j(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。
1 f2 t- D4 o9 f9 b6 x5 Hmsado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned ' U! m, u; r* d1 T6 h$ @: N
0 X V; Q+ }$ u4 {【3】创建Connection对象并连接数据库
5 Z. @8 N: z; f首先我们需要添加一个指向Connection对象的指针: : {3 X4 j# L! X$ ^0 |" E5 d% _
_ConnectionPtr m_pConnection; ! e0 y$ v, f7 o/ k: Z6 T
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。 1 I% N( V$ v2 m0 w# }9 a
9 N( \+ W, r7 v3 N9 E. j7 C& \1 w8 _( C" w" x& I( }1 U* Q( m
BOOL CADOTest1Dlg::OnInitDialog()
/ \( S1 d" O/ C% {: B0 q, |' b8 W {
& a( X" o: m: p H CDialog::OnInitDialog();
8 x! q0 L7 K @! h" i0 p6 w HRESULT hr;
5 X) s' M/ i; y: O/ U try 0 j. x5 N6 A$ X( a8 {5 f
{
+ c3 T/ |( n( k- N& W: { hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
5 N* o8 ^4 ~$ X# {! V) m [* C6 l, ^ if(SUCCEEDED(hr))
4 v+ S+ I$ K/ P3 e8 N. H { 4 k2 \$ E- c) R$ q
hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 # H* {9 }7 m' J. r
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; }
! M4 ?" F. U; @. Z+ \( a0 N0 P$ y } 7 `0 K" b% {- [
catch(_com_error e)///捕捉异常 9 f* q+ G5 v& G& |
{
, v/ y) D) h, r# g/ U! P1 X& @ CString errormessage;
0 F3 y+ b" \3 s* P" D errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); / P7 V; V- D+ x9 m) }
AfxMessageBox(errormessage);///显示错误信息
- e+ ?0 }/ d9 p8 p( y, K8 | }
. b: Y4 e6 Y; D# z- y7 q; ~
" t0 Q8 i- m. Z0 g6 R" N在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型 : D. J4 b$ G' I
HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options ) # D7 K7 @7 O3 k5 |; g$ d
ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权, 8 ^- s( _0 p/ K
Options可以是如下几个常量:
5 h+ i8 B9 H$ w/ @- y/ ladModeUnknown:缺省。当前的许可权未设置 0 J( P0 e+ P- R
adModeRead:只读 ; b& {3 { T; i
adModeWrite:只写 , k4 Y6 K( ?2 F1 y9 |% J- O4 C% f7 e
adModeReadWrite:可以读写 2 u; {- C! f$ {* w( v5 @+ r- s1 U# E) {
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
/ I& T6 a: @0 ~7 G8 M. e0 l$ ]# K2 M5 EadModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
& C; N" `$ h" o' C" ?adModeShareExclusive:阻止其它Connection对象打开连接
" Z8 n p! p" b: N. X# j( jadModeShareDenyNone:允许其它程序或对象以任何权限建立连接 : X, C) I/ K; O2 f6 |% m
j1 S7 T# q: q- M3 R* E我们给出一些常用的连接方式供大家参考: , O, F. n7 f+ c1 c& w
(1)通过JET数据库引擎对ACCESS2000数据库的连接
$ ^2 t: G& h% o$ P& ^- X- m g+ k- b: V$ h5 P( k. f
m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown); 2 ~( u! [ H& x
: n4 |; S$ b+ b% [* o- ^ K7 \# B(2)通过DSN数据源对任何支持ODBC的数据库进行连接:
3 P3 A2 L# T9 R, L. G0 j. Tm_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); " j& o' }2 `9 w3 M
; s. _' Y- P" y- N2 g(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown); + m, q# l' H8 k+ A+ y4 n, `
5 p* C& j+ \! a8 `# w) w; _其中Server是SQL服务器的名称,DATABASE是库的名称
3 }7 R6 z" ` w, }1 r& t) D% ~7 A. f% _9 {( R
Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State
" E% H1 ]" H+ }+ B3 D' vConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 + o# X1 z2 A) V
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown); : q2 v3 k$ m* A2 [9 @# K3 W
F: d8 l2 ^& w0 G5 `" H6 g" E8 o( Z# W4 o6 ?- T8 X, b) |
State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
' g4 a) A+ X, w% w3 I5 B E+ `( rif(m_pConnection->State)
, D0 r+ d' K3 h, e( ]# e4 p0 P m_pConnection->Close(); ///如果已经打开了连接则关闭它 3 V7 E" @$ Y! l) Z/ `3 \- n
0 K; _. ~7 ^" L* M( u' S
0 C4 y8 |7 M a0 l6 f x【4】执行SQL命令并取得结果记录集 8 g' ^7 w o' I0 `1 l% O! S8 x
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
3 N8 C ?) h& n8 Z. f- y* G3 R" ?* u" m2 c并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset"); $ K2 k/ z4 B/ Q' d7 z1 i
SQL命令的执行可以采用多种形式,下面我们一进行阐述。 ( F; ?% r% X9 V9 p9 t% K
, G! s7 }# G6 l" J6 O+ `, U3 ^(1)利用Connection对象的Execute方法执行SQL命令
" s9 R/ O8 f" Z/ K" yExecute方法的原型如下所示: 0 v: h6 C4 J P/ F
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一: 5 W$ ?$ X% S) z4 d
adCmdText:表明CommandText是文本命令 . w5 r/ @8 E% }0 c
adCmdTable:表明CommandText是一个表名 ! I5 Q/ f0 m0 n; X$ f G
adCmdProc:表明CommandText是一个存储过程 2 T0 p/ [2 b$ ~3 r. g( ^
adCmdUnknown:未知
( b9 V& s. n, L. K
8 v4 Y# X7 G" U3 {% _7 R$ r& T& BExecute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected;
C( D; \, d/ L5 M$ l* q ///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday ( d6 f' r. q% ]8 V
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText);
1 O, C- X% b8 A9 F- R" y, y ///往表格里面添加记录 / l: }# {4 v G7 G9 I: E
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText); ! o7 y( X4 ~1 `
///将所有记录old字段的值加一
. t, Y* [! F+ v! h* c6 q6 G+ c* c m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText);
$ E! w2 Q- `6 P6 }9 A2 E ///执行SQL统计命令得到包含记录条数的记录集
% Q; w- Y/ S. u, O$ N5 i m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
) u& D ?2 t7 ~" C3 s _variant_t vIndex = (long)0; ; O( w! M1 u9 C% G( M
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量
# Q j @5 d+ f+ k$ P m_pRecordset->Close();///关闭记录集 f3 }0 T3 e0 W
CString message; ! b& B2 a4 j% }9 T% C4 u0 o, [7 t$ m6 m& m9 b
message.Format("共有%d条记录",vCount.lVal); * m: E* V! N5 F u1 S' G
AfxMessageBox(message);///显示当前记录条数 ) Q0 z: J4 Y n' X
" J; m7 Y \% R) Y0 [
/ e: |- e- d" {7 O4 p(2)利用Command对象来执行SQL命令 " `# _, y1 {/ ]$ Q0 r& [( n- k
_CommandPtr m_pCommand;
3 f' b# V6 E \: Y2 b5 q9 m0 K) i7 q m_pCommand.CreateInstance("ADODB.Command");
9 W* E, H! j' c5 r, l# o7 v _variant_t vNULL;
7 P+ N6 w$ w! d3 Q: r; L vNULL.vt = VT_ERROR; ' R7 F& j" S7 L- c/ R4 x
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数 . h7 G$ E, [; Y! z& ?, G
m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它 % ]& }- g+ [" J
m_pCommand->CommandText = "SELECT * FROM users";///命令字串 8 u- ~: q6 a" R5 C( p- D
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
$ B1 {0 @+ p7 W) ^, W+ @# _- l
" n' B# j& P. o3 D1 m在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
) f: u% d6 p, y& G' @0 y& }0 i6 }6 |/ @: \. [. K7 a
' _' N7 H; N& m$ Y, r
(3)直接用Recordset对象进行查询取得记录集 5 a% Q& A! W8 u
例如 : `7 S% F9 ~$ V# u
0 S& [% H0 K6 R8 L* H
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
) w# ~% b1 o# w" M. K/ @. V8 e# P! X; P/ _( h7 e% G; e
Open方法的原型是这样的:
, k1 ~" m. ^8 g7 h3 vHRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ) 3 [, N1 G/ Y3 L1 k
其中:
' a- E& d" Q4 [5 `①Source是数据查询字符串 3 G1 y' [* j, Q3 F
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
- d/ l# I+ H- y5 p③CursorType光标类型,它可以是以下值之一,请看这个枚举结构: " E' d) ~9 p" u5 I
enum CursorTypeEnum % a$ }- @3 ~# z
{ " v8 [ X- s$ x4 M& D x' x
adOpenUnspecified = -1,///不作特别指定 ) f2 g: n, z( _7 v, Q* y
adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用 * W% G5 Y% X& H5 s) q. ^2 L& x
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
0 M" E& J9 G& }3 u! Q! z4 ladOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。 % D$ ^! s6 u8 ^3 W( p4 s
adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 ! {8 U; v- Q. f7 J9 }7 \) y
};
5 i' a) \/ k; O" l④LockType锁定类型,它可以是以下值之一,请看如下枚举结构: 1 I8 k+ Z, Z3 L- R1 `
enum LockTypeEnum
& p& s, T4 [6 b4 [4 `{ ) F( q" c1 z" s6 \
adLockUnspecified = -1,///未指定 ( S' ], ~0 W1 P, K
adLockReadOnly = 1,///只读记录集
5 S a4 H- t1 T: Z' O5 tadLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 : e. B2 a4 M" w, e( q
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作 6 b9 W& L4 x: ~& M, A
adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
: G$ `: c5 _/ p9 D2 L) `};
4 b1 l, ~2 h8 ]' T/ r! ~7 {⑤Options请参考本文中对Connection对象的Execute方法的介绍 " z4 m0 \# K/ A
4 e/ H6 W/ l7 O* V$ ^- Q
1 W4 N8 j% l4 d. j# h9 X, a【5】记录集的遍历、更新
6 ?+ b8 d9 q7 \ Z根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
5 ^0 ^: H! t# S6 F) s以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。
- X: d: C; O0 t! g2 c, V i3 R9 p6 o
7 o1 \- D) v7 o3 ]: h5 ^
0 P4 Z% [ X$ V5 D7 ]# q_variant_t vUsername,vBirthday,vID,vOld;
3 t6 R4 i- J4 p! K) d8 ]' h_RecordsetPtr m_pRecordset;
, Q0 g/ ]2 a# E8 l$ H( Z' l( m. am_pRecordset.CreateInstance("ADODB.Recordset");
$ s$ `& D5 _+ Y8 m4 i- em_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
& w& k" Q' H/ i# L6 \) u+ T3 Mwhile(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗?
v; M, d+ T8 N3 \+ b1 L: Y* z{
4 `: D! H. D! N$ [vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
0 X: }* E4 ~6 @" SvUsername = m_pRecordset->GetCollect("username");///取得username字段的值 8 N6 f4 C7 D2 B
vOld = m_pRecordset->GetCollect("old"); 4 ]3 r; q+ D: W* W
vBirthday = m_pRecordset->GetCollect("birthday"); ( J) d8 c% P" o
///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
, ]3 V$ A5 ?/ Kif(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
; Y. l8 \4 a" R( R TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday);
0 F& w, K% C( w- C9 lm_pRecordset->MoveNext();///移到下一条记录 Q6 p- T' h; v: Z' p
}
' B$ L( P- N" n1 }7 mm_pRecordset->MoveFirst();///移到首条记录 ( e" n% m* ]# y8 a6 C2 h8 b, J
m_pRecordset->Delete(adAffectCurrent);///删除当前记录
1 Z& ~* z* x8 H# u2 _# X///添加三条新记录并赋值 " \' i4 A v0 b# s
for(int i=0;i<3;i++)
: A, ^6 T9 L2 q: W{
- H6 C9 |* V+ ~' w& um_pRecordset->AddNew();///添加新记录 + G6 d" C' Y" k4 G6 ]
m_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
8 T: ]9 d$ r1 W8 t$ ?m_pRecordset->PutCollect("username",_variant_t("叶利钦")); : S: U4 _4 ~) C) l* R% h: r$ k
m_pRecordset->PutCollect("old",_variant_t((long)71));
# K; p: X' M/ M! a) Bm_pRecordset->PutCollect("birthday",_variant_t("1930-3-15")); 4 x$ z- k( z6 @) v8 I$ e, v! Y& c, i
} , f$ R! u. o' X) u8 [
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处 . p: D0 i! l, h& T5 R% W, y
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄
( M+ c7 |6 j& h' j4 ^. mm_pRecordset->Update();///保存到库中
/ d0 I9 Q5 N) }# B+ m5 @8 ?# Z6 ]4 c& N! D0 l
【6】关闭记录集与连接 ' D% x$ }7 [: G |/ n
记录集或连接都可以用Close方法来关闭
/ _0 A, J# K0 X" e- { m_pRecordset->Close();///关闭记录集 + G* [) W T4 x! e1 z
m_pConnection->Close();///关闭连接 6 q' T6 }7 R' H% h3 J6 g
0 [! _$ y4 b2 q' f# {& p( A至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。 " P8 _! r% b# _
点这里下载示例代码 ) `, a5 h0 B/ T; s7 n% e$ K
7 Z% i9 N6 b/ v8 D* t
后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|