|
|
一、ADO简介
, k5 m! I$ r) {2 y% K0 eADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。
7 V8 {" f; C$ D0 ~本文示例代码
7 W0 I+ b$ F i* \% ^8 _9 t" {" u6 t( V \3 A, m g
二、基本流程
0 T+ W# `- o+ \ F1 F- f. T5 `万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!
! ?4 S4 U( v3 C1 }0 a5 N6 f(1)初始化COM库,引入ADO库定义文件
. a, N' E4 _" o(2)用Connection对象连接数据库
% D* H' R; t& d( _1 f# W(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 ' S3 M3 O6 G8 X2 u' z: _
(4)使用完毕后关闭连接释放对象。
j8 ^% q7 n0 i0 r" \
7 f) X5 ]0 d8 Q [- ?' g准备工作:
7 y' J, F8 w& l: {3 e, E为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
/ I3 l# `4 t/ L. O& i# n* T下面我们将详细介绍上述步骤并给出相关代码。
& m- f; W+ p$ R: h5 D4 U【1】COM库的初始化 6 ?9 p1 R$ \8 L8 P) R
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码: 5 f6 M4 n7 l8 p( X- q1 S3 _
# I* R! [& x8 C4 _( I
+ u6 J4 h( F Q) }) HBOOL CADOTest1App::InitInstance()
' u* C$ w/ I7 X9 D' V: H {
# S8 V K' V5 V+ |, o AfxOleInit(); / Q9 l3 _3 h% e+ n9 K% m
...... 6 W( |' f( M- E* _/ I/ L; z
% M3 \8 r" v5 Q+ r N1 I0 M. Y
【2】用#import指令引入ADO类型库 , E. w, K5 G6 d$ d4 M8 M! e
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到)
/ l2 u! P$ S. i; U! G6 l, J. J#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
7 i, j: @4 ]/ K: X, ]4 W4 h这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
' g5 j3 o& F0 w1 F3 r- |/ C
7 a! Y) D" I1 N$ T' B+ e几点说明:
. f7 e! _! D, v& _(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改 & R% {) u$ _. b; P5 A
(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。
5 v* v ]: k1 f, r) F7 O7 [msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned
1 L/ o" l+ \, s0 [# {% a( o) @! ~/ u4 o
【3】创建Connection对象并连接数据库
) K1 t- c8 E1 T2 o' E M) O首先我们需要添加一个指向Connection对象的指针:
R9 \! X ~+ y8 ]% y2 ]8 L_ConnectionPtr m_pConnection; ! i1 t6 t: i" \2 V$ Y
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。
, p9 q% i& |1 E# s: D5 ^
% V" u+ ]6 c5 n+ E4 [+ K
0 T! \4 Z/ X e# Y7 r) I) A! NBOOL CADOTest1Dlg::OnInitDialog() ( j2 [+ C- o, w2 t
{
! ]9 Z! a# I9 E4 r9 U& @: F& Q( y CDialog::OnInitDialog(); 9 S: L6 J+ q! w+ N+ @9 ]
HRESULT hr;
/ ]5 ~ q* Q* v2 ]0 z try
- y0 D7 ]% f; j: l! ^# z { 0 O9 G# e( S( W% ]$ O
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象 , U- `2 W; J* V$ V0 `+ Y: M. u
if(SUCCEEDED(hr)) ! D+ W( n& k: @2 w
{ 9 O+ T, y* }7 {$ q! P1 D( x: _) H
hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 `+ G, _ h' G1 k# [" {
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; } ! Q9 A! G0 O- J( s/ b
}
2 [3 B2 V$ {" P, n0 i. ] catch(_com_error e)///捕捉异常
$ ^& m& D( s8 H t/ L {
; D4 I, }8 w# U) ^8 I CString errormessage; 8 o0 C$ e) h& {$ j3 v1 O, j7 T
errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage());
" \0 U" X$ A$ E4 j$ }8 K/ y AfxMessageBox(errormessage);///显示错误信息
/ C& a0 k4 |( D, A }
4 X: J3 @. L- s7 W/ E0 _9 Y# i8 E5 @& x9 n3 F
在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
5 I5 V0 \7 u8 c; M/ X& n* ^HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options ) 5 s4 c, Y W% p
ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
, u5 i% u5 h9 w9 u" i8 ?1 hOptions可以是如下几个常量:
I0 n! y6 b: @4 A9 nadModeUnknown:缺省。当前的许可权未设置
# l! u8 O, I' n6 e/ q! ^adModeRead:只读 7 N: u {9 d6 t+ H1 k7 L! ?/ z* q
adModeWrite:只写 ! Q( D9 ^2 P7 r) ^5 s3 m9 e
adModeReadWrite:可以读写
9 N( _: s! \9 n3 ^& t2 r: Q1 padModeShareDenyRead:阻止其它Connection对象以读权限打开连接 % j- k; q& r e0 y4 q
adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接
5 N! }/ t- {4 A! b1 [adModeShareExclusive:阻止其它Connection对象打开连接
$ k/ i1 Y X& F& w7 PadModeShareDenyNone:允许其它程序或对象以任何权限建立连接 6 }6 L! e6 {) x9 s/ c
+ `' S( }: m+ K3 N+ b
我们给出一些常用的连接方式供大家参考:
% }5 u! |: C1 b(1)通过JET数据库引擎对ACCESS2000数据库的连接
! d1 m; a o/ U6 _% f( a3 D
- S1 T+ o2 D! R( Zm_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);
+ H" d4 g( i* T6 O6 ~* ~+ D' g* p2 z! L* N
(2)通过DSN数据源对任何支持ODBC的数据库进行连接: 6 }' q! m" z, j( G8 o* g1 h
m_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); 2 O1 G: r9 d6 X) M$ ~
% u: Q5 W' W% K
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
1 B6 z2 o& g* L+ U! L; W
+ z* o1 h# l, Z5 L其中Server是SQL服务器的名称,DATABASE是库的名称
- @. u. E! r8 u. e5 h; M
/ t2 ^8 \! {) x5 b* O1 I( xConnection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State % E6 ~/ L0 |- j4 L) \
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 & ^$ c5 x- a B s: \! a5 o
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown); ! T) p; c2 u3 O' A5 O% B- n
+ h- o6 ]+ g: C7 r
E) r2 B( \- ]7 QState属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如: / b/ s6 f$ \% m Z4 r) M6 {6 w2 Q2 S
if(m_pConnection->State) # d) ]- h' Z" {6 K s
m_pConnection->Close(); ///如果已经打开了连接则关闭它
# |: Y w! h6 P! f- B) R( K, h5 n3 P& E, D! d9 {( b& Z! O4 N; W
) R9 g* F$ ^/ |# w: N9 |2 S+ h6 z【4】执行SQL命令并取得结果记录集 + S" I; w* c6 z6 S) F
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
1 b: x; }& U2 V; u( c6 U并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
" D( L B, M- L6 S! W& P* g* fSQL命令的执行可以采用多种形式,下面我们一进行阐述。 / ~2 V' @7 b5 g( y% R* O
; j8 |7 S% G4 L9 g+ S, Y8 G J+ l(1)利用Connection对象的Execute方法执行SQL命令
2 J: v2 J d$ M; B) ]Execute方法的原型如下所示:
8 g" Z( @2 X; p# {* f. B4 @0 G_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
, ?" T: \; N0 e3 RadCmdText:表明CommandText是文本命令
" F; n) p$ n7 q7 m% M, D3 uadCmdTable:表明CommandText是一个表名
. f/ [4 r5 c. c7 {' W3 HadCmdProc:表明CommandText是一个存储过程 8 ^0 ~) |5 A% c* A' F( X6 h$ a
adCmdUnknown:未知
! f; P# t7 q# e$ n8 S4 l, l* J- F( I( s
% L, C. u8 R) e: I( M KExecute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected;
: a& ?, J* o4 q ///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday
0 i. N7 H) U2 D# R3 @' d m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); # T/ y: W4 L+ B$ ~+ G
///往表格里面添加记录 2 [0 y3 [" o0 R' y! T8 ^
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText);
0 }- Z6 s4 `! B7 ? ///将所有记录old字段的值加一 2 z! l6 h3 {8 [/ P
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText); 0 G* s7 X1 y3 l3 L8 Q
///执行SQL统计命令得到包含记录条数的记录集
( O A7 Q" D% `: c) X$ H' n m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
# p; D: C; t* g1 T/ V _variant_t vIndex = (long)0;
" _4 L; \2 ^7 N! [$ {- X _variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量 ! r, c5 A* ?# `% V. b! g1 B
m_pRecordset->Close();///关闭记录集 & E4 ^0 \2 G: O" Y( [% l
CString message; & p5 s! I, L" g+ r
message.Format("共有%d条记录",vCount.lVal); + w$ ?8 ^) n* F$ m* \
AfxMessageBox(message);///显示当前记录条数 4 t6 F3 e' e1 O# R
0 z, |! t; x: U7 ]& q: g/ N. E
5 K) x' i2 v% {
(2)利用Command对象来执行SQL命令 c+ U: a1 G( j
_CommandPtr m_pCommand; 4 P( c5 d/ {0 o+ L. x7 O! j
m_pCommand.CreateInstance("ADODB.Command"); ' ?; `" r- Q3 R& q4 R, H8 n
_variant_t vNULL; 4 M/ C, @% k$ A- @
vNULL.vt = VT_ERROR; # N% W9 f' r! N1 N" y' `
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
" V% M. |+ c; q0 U* x& g& m- F m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它
8 L+ E1 F' l0 l5 p) `+ x m_pCommand->CommandText = "SELECT * FROM users";///命令字串 6 P& j8 J V8 G6 z }
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集 7 q5 c' v- ?% [2 L0 k
0 L1 g* k1 M6 K3 Y/ U) t- Q% E9 q
在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。 6 K1 m4 V1 |' Q8 u
) [3 ]5 h9 p! r+ f( C @8 A' ~% b. ~) r3 v. A: y- q1 z9 X, x4 Z6 I
(3)直接用Recordset对象进行查询取得记录集
$ C! \+ \: t- g1 M例如 ' ~6 D2 c* l& x' e+ L
H) p u6 N8 q( }: \ m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); ( {- i y; i6 r m2 J
6 i; I( `8 f* |; r# G" s: `; I; wOpen方法的原型是这样的: / E5 V, ?; e' u, R8 _" E
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
5 `% C7 s2 E; e3 w* T) l其中:
( R5 V0 W u' p d1 G, q: `7 \7 y, G①Source是数据查询字符串 % \1 O1 [+ S( W0 @$ H
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
8 n) m9 R4 _* x( Z③CursorType光标类型,它可以是以下值之一,请看这个枚举结构: 7 b3 k7 z1 M* U, F7 n( g
enum CursorTypeEnum
9 Q# y* k3 K, b' U: y- Y{ * U" C/ `0 {. U( ~" B$ \
adOpenUnspecified = -1,///不作特别指定 & g- ?) E1 T2 N
adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
5 }+ _" \5 _! gadOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
& W/ G/ T+ ~( @! M- b0 F+ ]adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
) t+ a! W9 U/ W! t+ padOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。
- J% A+ G$ g7 ~! P; q6 q! s}; ^' e7 G( f8 p# A; `
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构: / J6 a8 H/ N+ @2 g9 H b9 j
enum LockTypeEnum 7 j( c- G0 m$ n9 n; R) E
{ # u7 g d' O1 |0 n
adLockUnspecified = -1,///未指定
: t& O( F7 H, M; [adLockReadOnly = 1,///只读记录集
# M$ ~& L& q. R K: b2 ^1 GadLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 ( T6 ^) @- F9 W' r+ B
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
& P2 a% C& P+ VadLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
% m& h* P' _7 J+ W6 b' D ?# _8 r4 o}; 0 e+ j- x' }) T7 U" l/ n% g
⑤Options请参考本文中对Connection对象的Execute方法的介绍 7 a( q" D7 M. b# a3 {% E
$ a! D+ |+ f) |
7 q8 S! a/ C# g9 o- V【5】记录集的遍历、更新
: t, l9 d0 n* c$ c& K7 J根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday 1 V; j j0 R5 a9 c% B( D
以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。 $ Z+ H. e) }* ^2 y- ~
5 E" h, c* z; Z: E3 `) g
$ x5 H2 L1 l7 L/ `* B) M_variant_t vUsername,vBirthday,vID,vOld; ) x1 h' k6 V M J
_RecordsetPtr m_pRecordset; ) }- w% F& t s( d
m_pRecordset.CreateInstance("ADODB.Recordset");
; r/ P5 d5 N4 Qm_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); % n6 R. S3 j& o$ b6 ^2 V- V
while(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗? |( \' L; F4 R) `8 I
{
/ Y; E1 c; z8 C0 k3 S+ WvID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
$ }6 O$ p$ g C2 e- O8 z1 ZvUsername = m_pRecordset->GetCollect("username");///取得username字段的值
) E" d/ p8 I( O* U& OvOld = m_pRecordset->GetCollect("old");
8 K# W: b& Y( l/ JvBirthday = m_pRecordset->GetCollect("birthday");
6 m0 e% Q, A8 J! X7 i+ D9 I) |6 ^///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
/ Z0 a+ d. t6 [1 o$ sif(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL) ; C; |* ^% N0 z
TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); 5 B9 A+ }" |; @+ u' N. t! N( E
m_pRecordset->MoveNext();///移到下一条记录
8 O) D! m, G4 A5 v} * a+ ^/ K# Q6 _4 `2 E$ H1 l
m_pRecordset->MoveFirst();///移到首条记录
5 }+ | [8 v& q% w6 W/ w( a Vm_pRecordset->Delete(adAffectCurrent);///删除当前记录 % G( Z* Z. v. x) o3 p
///添加三条新记录并赋值
! {6 ^2 U5 z0 u' h: z% i" ]2 F4 }for(int i=0;i<3;i++)
7 S7 h. B6 o9 D, X( p1 k{ ~1 z2 ^) b6 A q( `+ y9 H( K8 @$ K
m_pRecordset->AddNew();///添加新记录 ( f6 l. ~) X$ c/ \' z8 c. R( V0 \& U
m_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
* _2 Y |8 k. R/ |/ e1 f" k/ }m_pRecordset->PutCollect("username",_variant_t("叶利钦")); 5 t" _' H; j4 s6 _4 d" X1 s
m_pRecordset->PutCollect("old",_variant_t((long)71));
+ p9 y8 {. v2 t0 h3 k" l' s3 l3 u* Km_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
$ h4 b' R# k" ^* T5 M$ r' l} " O: M [# ^- ~" J1 g: [
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处 ! |( ~: o. x3 `$ ~7 ]: @
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄 % ? L% U {9 C# D8 d; `+ f
m_pRecordset->Update();///保存到库中 5 \2 {9 r2 K O+ S; I6 U
6 U9 Q- I K# G4 ?【6】关闭记录集与连接
+ P9 g6 s; b# d9 N/ l记录集或连接都可以用Close方法来关闭 : k4 N1 c% x9 Y, {
m_pRecordset->Close();///关闭记录集
+ K1 Z- `" K5 ?( f3 y% b0 Y% L l m_pConnection->Close();///关闭连接 ' B u2 r$ U* v8 @3 U
' r' k) P7 y3 h, M. u7 J至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。 ; S# g3 F$ H6 C) H+ q6 o' F& u1 f2 ]
点这里下载示例代码 1 d7 Z/ \! ^- k0 S
% Y( j; G3 l& Y w- k
后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|