|
|
一、ADO简介 9 L4 P% I2 f# O$ o x
ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。 }2 Q T! s5 q: w) T" n
本文示例代码
; _2 ]; y0 h% s4 f( W3 U3 ]) N8 c
, @7 W2 J$ J' a3 @3 Z二、基本流程 * H+ G; F- n- i" i; [! T1 l" D' C
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧! 5 Z. z& A& W- s
(1)初始化COM库,引入ADO库定义文件
* E( M1 e# A1 Y& i(2)用Connection对象连接数据库
3 B3 p Q; G; x' B- d d% M5 B(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。
a2 H' f5 ] F+ i, y(4)使用完毕后关闭连接释放对象。 a7 h$ V4 n! V6 V0 F1 y
, O' Q" z4 ]( e2 `! x8 G* p准备工作:
$ z: ]0 q0 \. v! G8 `& {5 u# u为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
# h0 [( \! L3 i# h" W下面我们将详细介绍上述步骤并给出相关代码。
8 o# X) T9 I+ A9 J: ]【1】COM库的初始化
) M5 g* O) g+ G* d% P+ y' q4 `' `我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
& X: T f& w, [7 K) I7 d
+ Q- g6 F! k' n8 `
& C, o) v; f9 C& HBOOL CADOTest1App::InitInstance() ! y0 K& B+ n+ ~, N$ ]; A1 n
{
0 e2 L7 d s4 u" Z( b& ]& E9 D( r AfxOleInit();
0 s; \) G$ u" A( F$ A ...... 3 f4 g; y. ?" @/ b, j& e
5 k4 ~) ~: P% ?# c. g$ j【2】用#import指令引入ADO类型库
. z$ Y, v0 F/ \* J) ], D我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) % k" b2 V8 Z) t$ O9 t
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
/ f6 \4 s$ X4 d1 |( `: u7 T这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
1 e& Z; t1 \* `- q' t8 v
$ I! ?7 F% K9 _几点说明:
# \# r% N6 ^. T# S- T. A(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
) \* ^, z" p4 I' K(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。
- C& ?/ _' s; k( S6 c Dmsado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned
( \/ ^( Y& u" g. V! q0 U5 a8 p3 B7 O6 Z( Z* a6 J. z; ?. c
【3】创建Connection对象并连接数据库
! |$ [' @/ {; O( D3 h$ J5 A首先我们需要添加一个指向Connection对象的指针:
& d3 W- w4 r2 Z# U" {_ConnectionPtr m_pConnection; 2 A# e8 ~5 p N7 `
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。 $ \# O. i1 z8 j- u
, _$ f6 N! o* ]
0 @4 |% }4 T2 H4 R
BOOL CADOTest1Dlg::OnInitDialog() ! z9 D7 c4 A. e- i# h
{ 3 K' M* B' F. `
CDialog::OnInitDialog(); $ W0 I1 d" b/ F7 L+ l
HRESULT hr; 5 z1 f+ s% ?9 G
try , @( c2 \# F/ Z4 ^. I+ [' T; L
{
0 @$ G& G: B( Z* r3 `8 F hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象 6 a# a, M) g, Y. H' v7 ^6 r; M
if(SUCCEEDED(hr))
$ r9 N: ^. e) d. x# |) p {
" s: o1 g5 d4 O8 F, `& s hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 ) G% s8 d! y1 K: c
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; }
# n1 X: @( w5 S T1 p# W$ v0 E } 2 J( w! S" O& C# L) Y5 b
catch(_com_error e)///捕捉异常
/ k5 p/ @* ~! d' G: i% z# \ {
+ J: P8 D- S* d1 U# u& b0 p5 g$ `: j CString errormessage;
. g% C' \8 [& X' L+ b8 U( V errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); " D2 q; L6 n1 B. m R! L
AfxMessageBox(errormessage);///显示错误信息
0 \6 G' h7 {+ ? a }
4 Z. f# E) M5 T" s8 q9 Z4 t; G; b4 J. z4 l, u- |5 ?
在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型 % x! f1 G( W. M. Z3 `' ]
HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options ) 1 Q$ [# v* Y: b5 {% @4 `
ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
4 _3 o3 ?0 M" @+ kOptions可以是如下几个常量:
6 {3 n; c1 }5 X4 M% {0 W. JadModeUnknown:缺省。当前的许可权未设置
4 }0 J) _" ]/ S7 }. ZadModeRead:只读 + p V" U7 D0 H2 C4 A8 B
adModeWrite:只写
& A) U& ~6 }; ^& c V! [$ GadModeReadWrite:可以读写
5 q ?& ~( H; f" T. U9 {adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
5 ?; |) K/ i6 q, V- {adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接 & M6 k$ l+ n0 I
adModeShareExclusive:阻止其它Connection对象打开连接
) b% v- h1 j3 HadModeShareDenyNone:允许其它程序或对象以任何权限建立连接 4 `. m: g2 k# @5 v
5 V) Y; s7 M* I8 t% l6 O
我们给出一些常用的连接方式供大家参考:
% D0 _/ Q" T8 M5 z+ \5 }(1)通过JET数据库引擎对ACCESS2000数据库的连接 # n. |6 ?* g4 T7 _% ~! D; s0 ?# f: K" i
6 l* t, x1 ^( ]
m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown); ' }- d' ? i0 v* |0 A' G" [
# w' O$ ^/ Z' {# o2 B! D) ^! s+ E(2)通过DSN数据源对任何支持ODBC的数据库进行连接:
$ N- [! g* k' h' b4 Xm_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); ( [0 q/ a$ D" Z1 E
( D( i5 M) f- m
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
; O) u; M# `6 @+ P- L# k8 a
& }% m8 \" y# D% \1 a其中Server是SQL服务器的名称,DATABASE是库的名称 . i* Z2 C- q0 ~1 _) O
9 I, R% K4 _7 vConnection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State 3 Z+ ~8 z4 s2 v' W
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 / W; Z8 p1 `9 Y
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
/ m3 j8 E$ T2 [; k# @0 |: l: D2 U4 A H4 g$ h- |6 e% _
: K- H2 G( k! R
State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
; ~% N% P; c& A3 ^6 ]6 o; J# t9 jif(m_pConnection->State) 9 v& h# A, y0 B* m! `
m_pConnection->Close(); ///如果已经打开了连接则关闭它
) A- u# S8 X6 h" L5 q J
4 [. X& }7 ~# _2 b9 D: |- ~# n1 f
1 B1 L( q2 [7 N( m! W: ?2 l% l【4】执行SQL命令并取得结果记录集
3 P: R% x- h( \6 X: V+ H为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
0 O* y- q+ t4 g% u! t1 M# P并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
' U) j( w8 g6 F$ w7 Z% vSQL命令的执行可以采用多种形式,下面我们一进行阐述。
6 h. o$ ], [+ Z' M4 A! S" p. h- u# `. }; _
(1)利用Connection对象的Execute方法执行SQL命令 u! }& \/ n+ r! ~9 e0 N" i4 V6 M2 I
Execute方法的原型如下所示:
! [4 J3 W$ j/ G }% M/ X. u( x+ ~; i_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一: P- d2 w z5 ^8 p- g
adCmdText:表明CommandText是文本命令 1 Y8 V' y& {' @
adCmdTable:表明CommandText是一个表名 ( o9 C# h' }5 @, _4 X3 T
adCmdProc:表明CommandText是一个存储过程
. z) y3 Q0 A5 Z: r6 {7 w! madCmdUnknown:未知 ( a% W9 _ |0 C6 b5 \3 p; N
0 A" s1 i3 Q4 q8 H0 y0 Z' IExecute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected; ; H: Z3 c+ c* e0 z) X) K* d; T
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday : R$ ]: S, \* A; A$ h
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); $ n$ f# F: s5 F) T( T
///往表格里面添加记录 3 Z) J& K1 t# Z' N2 @3 d
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText);
# w) Z- Y4 c) j1 C1 u ///将所有记录old字段的值加一
3 J8 n, Z' r* K4 ?4 H# p/ M0 ` m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText); ! ?' t+ l+ }7 a' P0 j
///执行SQL统计命令得到包含记录条数的记录集 0 r7 v9 t8 u4 p1 N6 }: S) ~) J
m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
& r! F% o' I0 N. ?/ f5 {0 S* F6 y _variant_t vIndex = (long)0;
I" e7 x, I; K1 w _variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量
0 W% F1 x1 ]" K2 l3 X m_pRecordset->Close();///关闭记录集 0 c* t- F5 R) ~9 O4 j
CString message;
8 u5 y; c, x5 m7 A6 z% h2 i8 @ message.Format("共有%d条记录",vCount.lVal);
+ J$ |7 i; {! l$ q6 ~ x AfxMessageBox(message);///显示当前记录条数
7 |, D6 Z$ U+ c2 G! Z. }
7 l7 X: D$ l; }6 E2 E! y L2 x
6 i+ i. t: t* s% l# ~# ` l1 ]1 O(2)利用Command对象来执行SQL命令
) H* a6 X' [. N. v) V4 F _CommandPtr m_pCommand;
1 f' g- O& r+ L7 ? m_pCommand.CreateInstance("ADODB.Command"); . I& A. g- x2 J8 }3 M, b& n
_variant_t vNULL;
0 U; U2 d0 u5 ^, B2 V vNULL.vt = VT_ERROR;
! t5 I" c7 f. i8 n7 z1 P vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
7 B( Y" v3 F X/ @5 O4 b M m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它
1 R" `/ ]& v+ [- Q$ x m_pCommand->CommandText = "SELECT * FROM users";///命令字串 8 `1 z0 r( `' Q& d5 v
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
0 u6 X+ B$ ] Y( X5 _7 f# d0 q- x8 X' o
在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。 0 P! Q* n9 l7 c) s
) M, ^- j! m9 V3 f/ S
" F/ g: O% c& h# W. m; J; ?(3)直接用Recordset对象进行查询取得记录集
% t) a& f( q- W& @7 R例如
: Y3 ~+ g0 e. v1 S3 j2 `- ~5 N) U7 {2 n0 k6 o
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); 3 i' Z$ v. Z( G
: \* H. h/ z8 Q% @2 EOpen方法的原型是这样的: 8 y/ ~4 b6 T/ s6 J8 j
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ) 4 g6 |, w5 E- G/ ~0 c2 u. k
其中: Z4 K) D0 X' w4 V- ~; O
①Source是数据查询字符串 ' a+ E% ^% c- p j' D- k
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
- ?6 y8 ~5 S# W% d9 Y" O1 k" @③CursorType光标类型,它可以是以下值之一,请看这个枚举结构: & Y7 a' [9 C/ ^* h+ F1 m' j* R; r
enum CursorTypeEnum
! w2 Z9 L: |: o! S0 z: N8 L) c$ t) E{ 9 n/ Q( |5 m' N Y& R7 Q: a& R
adOpenUnspecified = -1,///不作特别指定
9 w6 {, h* M @/ m T2 }# fadOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用 4 t( `& h {0 |0 ]) r X
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。 , n2 p: M% e, M$ j; C' |
adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
" S5 W6 N2 _9 s; X! m* D. `+ O4 c, | TadOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。
& l# V$ M# U# f2 k& I}; 7 `/ q; g) H) H" c6 j" d% O
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
$ p4 n3 P! T K1 henum LockTypeEnum , Z) Q) r* R: {9 q B) p
{ % C+ p% w! | r
adLockUnspecified = -1,///未指定 ' i2 r6 W8 D% T3 y
adLockReadOnly = 1,///只读记录集
7 Q' H2 p9 e5 u8 _adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 / d& F8 l( I; i! a; ~
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
/ f8 [6 G9 ^4 P# KadLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
$ V' U% l! F5 P}; $ _; i+ d, c' S- q: [/ L5 O
⑤Options请参考本文中对Connection对象的Execute方法的介绍 8 ]( ?! k3 w& L' Z) A
) V8 t) v: ]% y' X- |& w8 }5 O! u2 w, U( `3 P! [# P' _
【5】记录集的遍历、更新
4 v5 Q7 G. m8 R3 [( L/ O根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday - u6 `! J& i ^3 t
以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。 " R2 u3 f; F. \9 G7 x. W
4 I; ^4 r* E- {( F, x; r4 J
8 _4 d6 X; O$ X' }1 N {/ Z_variant_t vUsername,vBirthday,vID,vOld; 7 _, Q7 J( H: M% H* B' k6 J
_RecordsetPtr m_pRecordset; ) p0 @. ?4 ]$ q. T% |( D7 X
m_pRecordset.CreateInstance("ADODB.Recordset");
( T5 ?3 K \/ S4 y8 g7 D. l2 I: um_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
5 K- t% Z; [2 ewhile(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗?
4 K/ e( v7 Q' t4 F P- W7 K{
! J) i9 p1 z: @vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
: E1 e: a2 T+ R: a5 {& ]vUsername = m_pRecordset->GetCollect("username");///取得username字段的值
# j8 l. M! m/ B; ?: fvOld = m_pRecordset->GetCollect("old"); ) o/ ~2 p1 j' ` ?9 i
vBirthday = m_pRecordset->GetCollect("birthday"); ' Z8 W1 [ Y8 z2 ^6 X
///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
$ y4 Z d$ `; C. r* F5 uif(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
( r% C% ?1 \( d9 a; n TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday);
- q+ D8 [3 ~ \' zm_pRecordset->MoveNext();///移到下一条记录
; @, A. X0 V( W" L- G}
' b1 y/ y1 I% w: O! w4 Lm_pRecordset->MoveFirst();///移到首条记录 3 S8 E) m! U8 t8 [! M3 V5 ?) e+ \
m_pRecordset->Delete(adAffectCurrent);///删除当前记录 / z, r8 z1 c- H6 X
///添加三条新记录并赋值 3 D6 }+ c6 k/ Z7 J
for(int i=0;i<3;i++)
/ y' u" l5 D+ W2 ~! d' A0 g7 t{ ( `. |- t) O' k3 {4 M) J& n+ T
m_pRecordset->AddNew();///添加新记录
6 q( ^1 j& E. G2 |% }! em_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
}' V! R$ u. E+ }: J1 c7 L7 ym_pRecordset->PutCollect("username",_variant_t("叶利钦"));
. I0 F" R& K- s% o5 s# b- s$ t" sm_pRecordset->PutCollect("old",_variant_t((long)71));
# u( ^* [, ^, W% W: M% ?4 qm_pRecordset->PutCollect("birthday",_variant_t("1930-3-15")); % c) i, F) S3 E& T3 n( `
} ' U" w/ P5 w$ O( j7 t
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处 ; k) Q) c' z0 |# e( O( _' e8 m
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄 , ~1 l+ L4 p. d; D2 ?1 O- Z2 Y
m_pRecordset->Update();///保存到库中 - H# ], h7 _3 l2 {" ], r. B
- h$ B& K9 P: A, Z8 m$ L
【6】关闭记录集与连接 " ?9 r: E) Q; Y0 w& s
记录集或连接都可以用Close方法来关闭
9 {, H5 O0 n) ~ m_pRecordset->Close();///关闭记录集 : U9 ?! R( O$ Q# s/ ~
m_pConnection->Close();///关闭连接 ; c+ {, s* {8 Z6 O" \
) ?, Y+ _+ L' K: `
至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。 : d( @, i- L% L- W5 ]2 G
点这里下载示例代码
! F7 y3 J9 M3 Y/ n3 i' N, j, }7 g
- R9 l- [' ~; E p后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|