|
一、ADO简介 9 L+ Z7 ?5 e& \1 z
ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。
# Q) Q) }4 v8 Q( |9 I8 o本文示例代码 1 r. c( C7 z, l1 e% I
. J7 C! o$ c( k( M6 y
二、基本流程 4 I5 l/ b8 j: s- E# v, G
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧!
+ y! V0 x( t, G+ y% F a(1)初始化COM库,引入ADO库定义文件 : d; }$ W W! u, Z$ d
(2)用Connection对象连接数据库 / T" T9 z( ?9 l
(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。
* O6 @* D1 b% ]4 c(4)使用完毕后关闭连接释放对象。
/ c) ^9 ?! w4 N
2 ~" Y/ F7 t# n$ f准备工作: ' ^) U' b! w1 S# w, G
为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。 - n5 e2 B9 b6 n. ?3 ^3 ^3 K
下面我们将详细介绍上述步骤并给出相关代码。 7 B% e" \: V" W; V
【1】COM库的初始化
1 d- J, `; {) U5 g, o [2 N. Z* e我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码: 9 Q& y2 w2 c y% Q" r9 |
" u0 I: _3 l; S8 q- E6 D' x- `
! C$ |: D% E2 q6 GBOOL CADOTest1App::InitInstance() $ M1 s1 Y2 s# f- p' z* O
{
6 v8 \* e, e6 R. q% S7 Q( V! Z AfxOleInit();
( h [, Q/ C4 r, r* N ...... + t9 G+ t: J5 g# u5 u
, M8 @- `: ~( k) {5 ^【2】用#import指令引入ADO类型库 / u. K: T3 s( c" y4 r
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到)
5 X! [; x/ F2 a4 }% r#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") " J2 o% b1 M/ r3 T; C+ d
这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
2 I* l6 M' G2 J" |! k# ^( w6 y! o" Y: Z$ v5 \
几点说明: " h& W+ @- y$ O! o) B
(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改 # }: B. N" Z- V' v0 J
(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 5 m) P+ {: B8 u
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned ( u1 t0 ]( [) N3 y
$ [( v0 f9 `7 \2 M# F, j# x, w
【3】创建Connection对象并连接数据库 4 S: F/ E" ?1 Y% F4 @8 U( c: y
首先我们需要添加一个指向Connection对象的指针: 6 x- X/ }# Q( l! [* K! V2 Q% Z
_ConnectionPtr m_pConnection; / `7 M7 x% u8 d+ \
下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。 8 E o7 H8 ^' [( x, z/ Q! W
& U: R+ ]0 x/ w5 ?' M' q
9 [/ {9 D5 b2 l, a! D3 g/ ~2 s- n
BOOL CADOTest1Dlg::OnInitDialog() ) i. t! q% l: U6 H3 n. X
{
6 c8 `! O# C2 X% c CDialog::OnInitDialog();
. t) Q, _9 M9 C+ x- n0 J HRESULT hr;
0 ?8 o/ o# h& {* H9 ` try : Z) t9 k1 [( z# p) }* ?
{ , P! J S3 L w) V; i) S7 |# }
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象 2 G! c7 H9 x' g( m) m2 |1 v, z; M/ H
if(SUCCEEDED(hr))
* T# r* b+ P5 s9 X {
9 z2 J* B w9 ?$ E8 C& p hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库
! H: W, o9 Q' Q: a0 ]# S5 l/ E( C ///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; }
7 ^8 ` b2 B9 o( ?& x } * G% ~" y4 y, p0 B! @7 m
catch(_com_error e)///捕捉异常 $ ^$ C1 k5 q* k; `9 k% H
{
0 D! j6 \& Y+ F4 e CString errormessage;
2 H$ D: J* \: ~ errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); 3 b! R, Y4 C v
AfxMessageBox(errormessage);///显示错误信息
/ s$ u5 r: C& g* a } % O0 X; z( z# K4 t+ ?1 v0 X# |
3 _' b2 I/ t5 h' s
在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
& p% n. J, j3 C4 d% o8 C' n6 mHRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
: J) w. u' |) z3 [ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权, 2 l' l: i1 K; \( @
Options可以是如下几个常量:
) n: s' T3 r, n# {3 n b& iadModeUnknown:缺省。当前的许可权未设置
1 c. L y' ?$ | m0 Q1 Y c0 I, ZadModeRead:只读 & f9 }8 l" D7 D$ n. l Z
adModeWrite:只写 2 K( |5 I- M3 J; B; i' D5 p! B
adModeReadWrite:可以读写 7 i' j" p4 h- y% w& V0 B( T
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
' ?9 a; X. v0 H) l+ w% r3 h1 radModeShareDenyWrite:阻止其它Connection对象以写权限打开连接 $ p6 |+ t0 G+ f* \) q2 ^
adModeShareExclusive:阻止其它Connection对象打开连接
: Q+ D9 L6 w+ u: Q4 \adModeShareDenyNone:允许其它程序或对象以任何权限建立连接
: q; M# V' @! {9 C, U& `/ D
: m2 P `6 B. {) ]我们给出一些常用的连接方式供大家参考: ' [# |( c' i6 O \
(1)通过JET数据库引擎对ACCESS2000数据库的连接 * W( j+ I- S# L8 n. Y
: ], m# n T* e/ w+ Q- a0 km_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown); e6 R4 r5 Q) u( S
3 U. x1 M+ e! d" K2 m(2)通过DSN数据源对任何支持ODBC的数据库进行连接:
" M# P" @" j6 c! |' C9 t4 Wm_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown); 7 P! W' _7 p6 b; G3 x; {
1 |( X8 j v c# B* @
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown); + x& C9 t7 I' K
0 w0 q4 H& D% v; T0 M3 ]" N5 A
其中Server是SQL服务器的名称,DATABASE是库的名称
# i1 k w4 U9 T: W9 k% Q. M1 C* a
Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State , v7 ]6 M; |; J6 H+ Z2 L
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒
8 K2 O6 E7 I. @4 A: tm_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
6 |( b) @4 R' n, T" w( x. [. x' J$ e D5 ?4 d5 @
' M6 [1 W% k& k4 X' l% E HState属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如:
6 U# p6 Y7 u, t- y- m3 \& R% h Tif(m_pConnection->State) 6 t7 N5 @2 t2 d5 p0 {1 V
m_pConnection->Close(); ///如果已经打开了连接则关闭它 ' o; T+ z: z2 i
9 S* g& U; z* m4 ]( {$ g7 U p; ], W, g/ p: n1 z
【4】执行SQL命令并取得结果记录集 0 t; a1 h8 o8 i# }5 R& z
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset; $ J: W0 G1 C8 Y$ L+ P: w, W
并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
& Q2 R4 L/ l9 NSQL命令的执行可以采用多种形式,下面我们一进行阐述。 ( [7 B# u" @, }6 O e) R5 j
+ L0 W. H' n. e. E: a(1)利用Connection对象的Execute方法执行SQL命令
8 \3 ]1 w% D9 W$ _1 {Execute方法的原型如下所示:
3 `4 O$ l9 m- `& P9 B% e7 N_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
/ ?7 N: P, y1 t% v2 r) tadCmdText:表明CommandText是文本命令
6 b K h) R4 D0 F* h0 r& radCmdTable:表明CommandText是一个表名 9 a/ g5 m. M, d
adCmdProc:表明CommandText是一个存储过程
; g. P, o' R& q% MadCmdUnknown:未知 7 j$ V& c6 ^' Q6 Q, J
0 V- O- ^( W: ]Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected; 9 ^+ Z" w9 M7 Y! ~3 p c3 e) Z: z! O
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday - J/ o! B/ r4 z5 c" `
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); 5 o' r- K2 _: n( R" T8 g4 _
///往表格里面添加记录
- n% G I2 o9 A( U3 R m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText); , i y3 `- Z. Q9 C
///将所有记录old字段的值加一 . u; k( A ^: w8 D% Y
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText); ; j% i. L, p0 K) W% ?0 Z
///执行SQL统计命令得到包含记录条数的记录集
# y& }! {, |& c m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
* b+ H! y% L6 M& w A _variant_t vIndex = (long)0; & `" T4 |( H6 F, g. z8 [
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量
+ C( l, _& m! r' ~4 `; S# @# Z% g m_pRecordset->Close();///关闭记录集
* }9 i- j- I p3 u( B CString message;
! E/ P& M8 {/ q5 u7 W/ g message.Format("共有%d条记录",vCount.lVal);
# o" h4 ~( ~0 K5 W% a5 V4 `4 v AfxMessageBox(message);///显示当前记录条数
- G$ H' ~- W) m K9 h4 Z6 H( o5 X2 y6 `
+ |4 f( X- G, d+ s1 o1 y6 ?
+ ^& V- U2 |% n: |% b z; F5 ^( h(2)利用Command对象来执行SQL命令
4 ~; l4 R+ C- a _CommandPtr m_pCommand; # K p# r( g' _
m_pCommand.CreateInstance("ADODB.Command");
6 h# ^$ l$ j% t2 \3 Q. M _variant_t vNULL;
a, Y$ k0 K- m( L5 _ vNULL.vt = VT_ERROR;
& k) ?+ R0 j! q% {4 _ vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
8 K- t8 R, E t: K% B: v5 _6 q3 ? m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它 3 k$ r) a+ X2 ~4 a! t2 f/ c; o- f
m_pCommand->CommandText = "SELECT * FROM users";///命令字串
, M" g/ s/ K$ U; U- G* e4 v2 V; a m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
& j$ u, a8 G0 V" y) a/ v! L' Y+ z- f1 k& s% W% h8 h
在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
& _0 i2 u7 R0 h9 ^
& l5 r& [1 {$ e! m5 e' x# j* |+ T" q1 R0 f- y
(3)直接用Recordset对象进行查询取得记录集 ' a* W I4 N6 L2 _( |* a
例如
5 H; V7 F. S2 k. J3 j0 W: h+ k: J7 i& K) @! l% p, P; y
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
: ]2 l1 T) Q+ I: E% _5 R; Y9 l8 r
4 ^: E. D0 n! @6 y: _Open方法的原型是这样的: + @+ K3 E& K) F' k- F
HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ) , n4 F+ V1 z6 N2 \ f
其中: + _* i9 [6 f+ b; h' P) T
①Source是数据查询字符串
& o9 q: N: j" z- N0 T; i& ^②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
( n" l+ g+ m$ Y+ O7 D③CursorType光标类型,它可以是以下值之一,请看这个枚举结构: , l5 c5 D! x" @8 S/ s
enum CursorTypeEnum
, `* w% a; B2 I9 j, C{ * x+ d! [" C c( K! i/ M# V* X# |
adOpenUnspecified = -1,///不作特别指定
7 ^+ l5 o' ^; G" padOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
; f/ T; O( n* ~adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。 4 s* S; C) U/ f5 r. d+ Z+ c
adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
% L+ l0 J0 v7 F4 _( N0 LadOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 6 |6 l* L8 _8 k3 y
};
3 O; l' o6 E" W, l& }④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
/ E$ E1 @1 E" F! ^) yenum LockTypeEnum 5 L/ |3 Y6 J( J- g$ n$ a% Z
{ ; @5 }$ p6 a3 B7 N6 ]' D
adLockUnspecified = -1,///未指定 . ^; Y) B, p6 e
adLockReadOnly = 1,///只读记录集
: ?4 s* q: }4 Q4 ?* e+ c! jadLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 ! a& P9 p; M K7 n& E
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作 ( a! j6 A9 G: n; b2 r! }( B( t
adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
i! E$ @: F/ M& h L: Y, z}; ! c( _6 K9 w4 t0 y' s
⑤Options请参考本文中对Connection对象的Execute方法的介绍 1 M" t6 t6 |& G3 u' z, w. J# M
4 I" A* ~% S1 F
, f, u6 s% j) L, C- w
【5】记录集的遍历、更新 * F5 |/ y: I8 ]8 a
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
1 Z; ]8 U% A. k6 F/ K以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。
+ x7 Q' R; r5 m8 S0 x. ]- [) _
3 V( P) {5 s0 a+ ?0 I" ~& `9 G P0 y
_variant_t vUsername,vBirthday,vID,vOld; ! f* N2 ~# |5 c; Y$ X d' M* h4 i9 p
_RecordsetPtr m_pRecordset;
* V1 {+ m9 q/ @! Lm_pRecordset.CreateInstance("ADODB.Recordset");
1 X+ D4 F% D) i) {2 B" _m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); 7 }% w) n. B$ g- a
while(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗? ' C. C P; D- _; P& q8 a
{ ! h4 @. B& x! p" a+ [
vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
: v0 H6 u s+ ?) C( r3 b8 T9 JvUsername = m_pRecordset->GetCollect("username");///取得username字段的值
+ Y6 O1 U) b. Q3 ovOld = m_pRecordset->GetCollect("old"); $ q3 U; m* j' G7 C/ ^6 J6 b" g/ f
vBirthday = m_pRecordset->GetCollect("birthday");
0 O) j, M. Q; q) H///在DEBUG方式下的OUTPUT窗口输出记录集中的记录 ( T# `3 \6 B3 m$ q2 h7 `
if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL) ! Y( u1 ` U! Y' b
TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday);
7 L- L Z0 w$ S3 w+ e9 j8 ^6 `m_pRecordset->MoveNext();///移到下一条记录 3 d$ s; m5 Z& L6 ]# A; C
}
A/ c0 \* _2 I; k6 z, h) fm_pRecordset->MoveFirst();///移到首条记录
8 a1 ]/ J- M- p8 N+ o/ Vm_pRecordset->Delete(adAffectCurrent);///删除当前记录
2 `& G" t5 f5 ~ x2 \7 p///添加三条新记录并赋值 & u6 f) i* A I2 n- O t
for(int i=0;i<3;i++)
9 I0 B% \/ o$ [' A% z# h{
; R% \6 X+ M( t5 ~m_pRecordset->AddNew();///添加新记录
- A6 H5 `9 O$ Dm_pRecordset->PutCollect("ID",_variant_t((long)(i+10))); 1 E1 Q' ^, T/ s. O3 ^
m_pRecordset->PutCollect("username",_variant_t("叶利钦")); % P& y; C. e. _
m_pRecordset->PutCollect("old",_variant_t((long)71)); # k/ a$ Z1 @5 x8 e1 _' y
m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15")); 6 z% @3 \* b _, A: r
}
! u8 d" {2 m7 H9 u2 k g, ^, jm_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处 . \) t, w8 p- F9 B4 ]! T) t$ s
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄 r' e: G4 y! e0 A3 o9 b& y
m_pRecordset->Update();///保存到库中
* h, }8 B# \, r1 A1 H' O; E$ }3 S( T& w& G& } E- b: x
【6】关闭记录集与连接 " R5 K4 A% b W1 g% C
记录集或连接都可以用Close方法来关闭
8 [ z% T/ S" t/ z m_pRecordset->Close();///关闭记录集
. W+ |3 q: I, z8 `4 O m_pConnection->Close();///关闭连接
- K0 I$ `' [0 B1 P+ b1 i7 J8 e& c" G* B; y% e3 ^
至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。
0 g6 V8 O& F+ h点这里下载示例代码 # F% u: J9 }" b+ v e9 v
7 S' G3 q8 Y1 O) e0 ^% O. a2 \8 Q; n
后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|