|
|
一、ADO简介 " T7 u* t Z s
ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。 ' L3 q* A$ z) C
本文示例代码 / v" o0 A4 g, \
# N) L" q" l7 R C( u1 f二、基本流程
9 s$ b" h, s+ S4 {" _万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!
% f# P- ?" q% |/ f i(1)初始化COM库,引入ADO库定义文件
, U1 X( p& b& x1 y7 c1 C b(2)用Connection对象连接数据库
/ L% ^1 }1 J# h: X9 _(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 7 ]- \9 ?6 S! h7 h9 A1 o. G3 X' ]
(4)使用完毕后关闭连接释放对象。
" {) C# ]$ [1 D: l0 h
1 E$ a" g) f: y5 q% }% L准备工作: ! `1 Y; E3 l$ w) Y" w$ [( t
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。
) C2 R- d* n1 B# {0 p下面我们将详细介绍上述步骤并给出相关代码。
: X+ Y1 m( f; s: l【1】COM库的初始化 - u$ m1 s/ g6 f4 b
我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
/ T+ d& s: W' [" x' ?$ y/ i7 I$ f0 C" Q# J; f
2 ]( W4 O; P6 Y7 z, ^. F
BOOL CADOTest1App::InitInstance()
4 V6 G" R+ i) S' B {
- M4 ]! j# n Z AfxOleInit();
1 K" F/ V; g: N( m) p: W' k ...... 1 d) z+ Z$ o0 t% j) Y2 O
0 `3 O9 n7 I: T& g
【2】用#import指令引入ADO类型库 ! x4 D/ y5 L |+ j* b
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到)
6 k* a9 [% A3 }$ \. R#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") - {2 k1 X9 K0 L5 ^
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
& f2 x" c5 A9 U( v* P$ y% ?5 G& f& g# t" j2 E4 y3 c
几点说明: 8 n4 g m0 l% H* k6 C
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
) M4 f0 L. {& ^/ q- x(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 . H- h, L& [ B; i8 T# l
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned # P. Y' v. P0 h. |0 T u Q
$ v J5 _0 \- \% H9 V* ]$ {3 ^1 ]
【3】创建Connection对象并连接数据库 3 |- b& I' T+ @2 `( Y1 U% j( z R
首先我们需要添加一个指向Connection对象的指针: : X. x K6 e# R
_ConnectionPtr m_pConnection;
; O8 \9 ]* s, A8 T; ?下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。
9 d$ T& }1 B) w; R* r! h7 R5 o7 P8 i
+ m0 A4 P G$ Z# n8 q4 D3 N5 o! |
$ c v C5 D+ `" D: ` {BOOL CADOTest1Dlg::OnInitDialog()
D# P2 I8 W5 M9 R9 C' e {
( I8 e L/ b* L; ?5 G+ c% f" o6 T CDialog::OnInitDialog(); $ ~ G- a: c2 ~
HRESULT hr; ( q' i1 w9 V g7 l* P7 i6 f8 `: V V6 H
try % W* T5 S x% v4 E# O: s
{ , Y( j5 \% u- u& B l) T( [ D Y
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象 0 U( J* V2 B$ f( a
if(SUCCEEDED(hr)) . R' i& l+ ^) J& W2 R
{ 4 ` a' K0 Q: h& z- k
hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 " h9 S7 P5 q) D: V9 K$ G" Y
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; } 4 H# \+ g% x4 C; r: X
} ' Y; @. R0 R1 o7 G
catch(_com_error e)///捕捉异常
P; ]0 d, b0 r) c {
* K3 g9 w0 T( P* [& S" O CString errormessage;
X ?3 X5 o6 h. z errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); " Q% W* i/ f2 n3 M- `$ \- g
AfxMessageBox(errormessage);///显示错误信息 : H; v3 i8 w8 \8 q) _
}
* h+ u# c# _. V, z, N b+ v+ @7 P# z% t" {% y, G6 Z
在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
& z7 G* L" o+ N' s3 c9 v5 iHRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options ) % }1 a3 h' i0 {! I& k L# z _0 j, }
ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权,
s* C! d# v0 p& V! o' X6 T5 nOptions可以是如下几个常量:
+ N9 {$ A2 a( dadModeUnknown:缺省。当前的许可权未设置
: `' V* |6 J5 c6 d1 sadModeRead:只读
8 ]4 H! Y: k8 }) l- G. W4 Q7 v! eadModeWrite:只写 / X, S/ C, |4 C4 ^, o1 Y2 r2 \
adModeReadWrite:可以读写 2 |. z7 F3 B. U: r5 T) _ p) u/ `. g
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
% R: }# R) ]4 f* C. badModeShareDenyWrite:阻止其它Connection对象以写权限打开连接 c, i& ^% E6 C3 i: z+ ^& q. M
adModeShareExclusive:阻止其它Connection对象打开连接 3 T& e' e! e8 P# Y% |; z( r
adModeShareDenyNone:允许其它程序或对象以任何权限建立连接 7 `& Y# `/ e9 |4 S3 n
& l" O/ l: ] }! |# `: P+ M- A) @
我们给出一些常用的连接方式供大家参考: + T* d! N0 x; i3 \- A3 E6 M
(1)通过JET数据库引擎对ACCESS2000数据库的连接
1 T* {: D1 i$ a& u( D2 L; d$ e. q0 U4 K; D( {" m
m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);
7 G( X5 ^1 A, r( n% u/ m" B7 @" S
(2)通过DSN数据源对任何支持ODBC的数据库进行连接: 0 h7 ^/ m4 o6 r7 X1 \ C/ Y
m_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown);
. Y$ v+ a/ e8 p, m6 H6 m. C7 Q7 Z7 Q! {( F7 f0 G! q( P
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
' n, n0 c* a. t
$ j# v* Q* d6 h1 f2 n X, ^其中Server是SQL服务器的名称,DATABASE是库的名称
: @* C) \: }- y9 u! q3 I
/ K& ]+ T. E# Q- f" JConnection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State 0 ]3 u$ w; z. p
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 6 g0 [4 I: k9 A/ C2 B2 i
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
- ~* ?; K" S& f9 C7 ^; \- c( ~9 \7 ]) [9 L
! |; I. u5 s! j( w: j9 }7 a( h0 CState属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如: % r3 X# K2 J. u+ N
if(m_pConnection->State)
. W+ |: K# d+ c5 C" q& k0 N+ j. w m_pConnection->Close(); ///如果已经打开了连接则关闭它
/ r9 _# {( A5 Z2 f8 s
* B8 f: \' T( L& K3 \( F/ N" j' p0 k0 y! A4 F
【4】执行SQL命令并取得结果记录集 5 [+ ]+ d0 O# Q9 b
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset; ) Y: V7 B7 q$ p& v
并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
8 x% H) g7 W' E6 f% I" ?8 e9 h1 dSQL命令的执行可以采用多种形式,下面我们一进行阐述。
/ b0 p, r4 V, f7 G( W* D8 h+ s
% m3 J( S4 W% i# e1 e# u# c(1)利用Connection对象的Execute方法执行SQL命令
" L; m: V [7 O: W& ~1 o) fExecute方法的原型如下所示: : g$ ^( o B5 |) b% }# `; i
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
/ E. D7 g6 j! a5 b- L5 x% FadCmdText:表明CommandText是文本命令
; L4 U4 z# b# ~! fadCmdTable:表明CommandText是一个表名
8 c2 [4 {1 o9 Z+ I8 MadCmdProc:表明CommandText是一个存储过程
0 C2 f" m! v7 Z' d6 W9 m5 oadCmdUnknown:未知 . l/ k2 k" _6 Y, {
* h6 |8 `8 @; L3 ^Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected; , t3 H( p% w9 K* s0 f3 Z; [$ Y! e
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday & C# l! N' W; U0 N
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); 8 D) S5 h" g8 k. Z- t* |
///往表格里面添加记录
$ f- r! m% Y1 {/ d: s m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText); & e) i, O, K( E
///将所有记录old字段的值加一
# T) q% r1 U& O. [% g% R; |% D m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText); 0 M( n0 W5 p- U1 y0 D
///执行SQL统计命令得到包含记录条数的记录集
% v3 i; s- Z, Z! p; B5 w m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
) D9 a# P4 [" o2 F5 q8 U& f _variant_t vIndex = (long)0; - x+ q& h+ v( {- i4 W
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量
3 i; h- R( p! i+ i m_pRecordset->Close();///关闭记录集
; }& E1 K+ q: Q7 D9 a0 H- D4 H CString message;
5 N% m o7 E" \3 B message.Format("共有%d条记录",vCount.lVal); ) T! z" E; x: Q* P2 s$ V+ B
AfxMessageBox(message);///显示当前记录条数 * ]9 l" |' z4 w1 s3 P; O( T; [
7 T/ p5 b) b+ B: v9 X+ p& ~
$ d1 \% h6 O" g( N8 j; j(2)利用Command对象来执行SQL命令 9 m# A( B# E& e
_CommandPtr m_pCommand;
' @) y U2 v. h, d- C4 s9 `! c. x: N m_pCommand.CreateInstance("ADODB.Command");
9 s" i* c2 _) ]( d) |4 _7 Z _variant_t vNULL;
9 D( M* A2 S, }: F% X vNULL.vt = VT_ERROR; 9 ~3 U9 c) K ^6 J4 T
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数 b) F! ]5 E; P0 C
m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它 3 D* \; [: ~% G9 ~* i. U
m_pCommand->CommandText = "SELECT * FROM users";///命令字串
4 n+ Y8 M8 K6 K- B# @/ o* ?6 l1 L m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
3 {2 D* l4 ?1 u1 G" E
( y |+ N2 }: }; O9 T! R+ |; s0 U在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
* Z6 m- @" D( Z4 s' {2 X$ g _; ~+ w9 A+ B8 F: Q; B' ?# e, ~$ H+ z- L
: n$ I1 b& c3 j! J" i) W
(3)直接用Recordset对象进行查询取得记录集
5 G4 p0 K& V$ S6 z* G: } {例如
; E, x! H& X" j
, E1 n: Y q$ b6 } m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); 6 M! J9 S% m$ L' h1 T1 C
0 d, L% j- q1 u; m/ ^Open方法的原型是这样的:
6 b; s5 j+ K8 \" `HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
6 X0 U. N% F B( U! N其中: & q# t. f, d) f2 ^
①Source是数据查询字符串
$ x P$ w- |; n: `9 B5 l②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
2 h8 j" }* F% v) W( Q③CursorType光标类型,它可以是以下值之一,请看这个枚举结构:
6 L. U r" V5 \2 M! K0 ` qenum CursorTypeEnum m# M( g; I5 X0 B/ @
{
, ~* Y* E! Q2 _( H" tadOpenUnspecified = -1,///不作特别指定
0 W0 U( V8 m1 P4 f ?+ d7 o. WadOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
9 x6 w. S8 D% X2 IadOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
! d3 q: Z/ p" l Z2 Z4 I6 W3 T! j7 radOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
' ]. |# j! o2 p& IadOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 & ~; z$ X' n5 \- x
};
/ L) [& U, {' }" `8 z6 ^& O2 w/ R$ s' r④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
+ ]+ `" N* d1 J& y4 renum LockTypeEnum
9 N) t; E. M d) E6 C2 j{
4 g8 M. o. X# ?adLockUnspecified = -1,///未指定
( F' k7 @5 f, e+ }, \adLockReadOnly = 1,///只读记录集
4 q# J/ f# H+ ]adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
: p' r. Q0 t, r9 p' [- e6 dadLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
5 _0 X9 H H& B7 xadLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。 : `2 K3 S/ v! p8 ^ P% M3 D0 i. L
};
* ~ Q# X" U- Q3 h⑤Options请参考本文中对Connection对象的Execute方法的介绍 5 O! v3 A" m" o* W- W
/ V4 H7 Y1 z3 c* W/ q. k* |
+ h3 d' O- l* w% y( ^$ w) t- Y$ Q
【5】记录集的遍历、更新 + K: y. r/ H& O
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
- ]5 q0 D$ H J, Q7 S/ O% x以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。 ; @. r2 J# {+ q( L: _+ y
2 I( g3 y- q L$ i" ?# c) o' y2 V
' k: I6 j S: C, i_variant_t vUsername,vBirthday,vID,vOld;
p/ K; Y3 S: l0 \% [_RecordsetPtr m_pRecordset; / w6 Q2 P0 K7 |, w5 M0 y! T
m_pRecordset.CreateInstance("ADODB.Recordset"); , O- z7 ?( m* ~3 W
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); * m8 }* U# Y: O+ ?! y: y
while(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗? 1 e6 A! q/ n# w4 Z, C+ a7 _
{ ! s! S9 P. I! t' w1 Y$ V9 D
vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
. d: A& ^' w' y6 n7 d2 KvUsername = m_pRecordset->GetCollect("username");///取得username字段的值 % l5 a) W0 w9 e5 i
vOld = m_pRecordset->GetCollect("old"); 7 }- o$ K1 h) k9 ^
vBirthday = m_pRecordset->GetCollect("birthday"); 8 Y2 M& l U) d1 o
///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
9 b6 d6 [6 d) a8 Y F; A$ Vif(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL) ' I: |: u1 m4 z e( M2 O1 f$ M+ S
TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); # z8 [4 I" B) Y$ e) f* }
m_pRecordset->MoveNext();///移到下一条记录 8 i( w6 z, E; K5 c2 ]
} # H( q% o8 B' W: G& p& y, P% J
m_pRecordset->MoveFirst();///移到首条记录
- N0 _2 c6 p. z2 ~3 T' i6 R- H Om_pRecordset->Delete(adAffectCurrent);///删除当前记录 7 a A4 D% a0 d( B6 `# A2 R
///添加三条新记录并赋值 s6 s0 x( K9 D5 L% H) n! v2 W* H
for(int i=0;i<3;i++)
6 R4 h+ N# }9 P, f$ U4 Y; V8 J{
2 V8 o! O ]" s3 l7 Hm_pRecordset->AddNew();///添加新记录 7 l3 E0 J/ t; L1 R2 l( p
m_pRecordset->PutCollect("ID",_variant_t((long)(i+10))); , M, w9 g2 a- h( i& f! h
m_pRecordset->PutCollect("username",_variant_t("叶利钦"));
6 g& }+ J1 |, G( P8 \. T/ Lm_pRecordset->PutCollect("old",_variant_t((long)71));
* g! V/ D: U8 b2 Ym_pRecordset->PutCollect("birthday",_variant_t("1930-3-15")); & F- f/ z; x1 t3 m8 f& y) ^3 }
}
: c& U0 S( F7 Y, ]/ x9 ^. Lm_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处
% R% l0 m3 e6 ] gm_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄
# I2 n% a2 I) ]# H! u( ]" Gm_pRecordset->Update();///保存到库中
% E- V: j/ o# |! U. t" M# e$ @3 F) T. }6 s
【6】关闭记录集与连接
' h, V- w- L( s. M& {记录集或连接都可以用Close方法来关闭 1 f. ? x. ]' X* O1 @0 K
m_pRecordset->Close();///关闭记录集 ) n5 B$ v3 {; R$ ^ \. N
m_pConnection->Close();///关闭连接 9 g& h- a# N1 i5 \( g& z( w$ ?& \
/ p4 g5 P) f; ]! M1 }
至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
/ n" J" b8 \4 g+ [点这里下载示例代码
, i& h8 |# L+ @! C0 F& C: t
; R e; r' ~( }后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|