|
一、ADO简介
! C6 w7 _1 b/ V/ E/ [7 Q0 @! |ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。
; H/ F4 w" i, R* O本文示例代码 , l6 C6 i4 |+ }9 M# q8 D0 U0 H" y
: G" e/ h- t9 f1 ~. [# d9 p' @2 Q- [二、基本流程 - ]5 p" n b) [; Y& x/ [
万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧! 3 |$ X0 q3 \. s2 \2 t" m" _
(1)初始化COM库,引入ADO库定义文件 $ E7 r. c& ?+ i5 n
(2)用Connection对象连接数据库
" B; O- G* t/ [1 L, ]( F(3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。
. e% u1 |8 S1 ]# f# m(4)使用完毕后关闭连接释放对象。
) m7 _% V5 Z9 a6 y0 y" t) b! k9 B5 I& Q1 ]+ [) r4 P
准备工作:
: C3 r+ Z+ F7 c0 B9 Z为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。 0 S$ k& L! w9 y& F9 O& y( B% x
下面我们将详细介绍上述步骤并给出相关代码。
7 [1 J9 n+ W5 f3 S【1】COM库的初始化
- t& Y" e- O1 W7 K# ^我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
d8 f0 x2 a9 i' C. U, C3 k5 i9 }' [' L5 Q: X
6 e, y4 T& m2 [- k) ~, S
BOOL CADOTest1App::InitInstance()
! b, |6 N i8 p) \# O {
8 t& ^2 M4 a" Q! x! y6 X! p AfxOleInit(); h' x; r8 K6 @0 Q/ r) J3 `
...... / f9 ~, R0 T, ?' N1 B( r; H
- C9 P+ t$ b, W' @【2】用#import指令引入ADO类型库 3 X8 F, n |# z$ f. i
我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) 2 @( N- M i9 r6 d4 v
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
' S- h3 y2 U- }. S) H/ H这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
0 A5 a& Q/ f* ?* V
& a+ Z& N5 G' S6 Z: a' z几点说明:
5 |: e! B" j/ j! c d+ @+ q(1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改
s7 ?; n+ G }" B, J' F# A(2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 ) H* |, e4 N1 c; w& H4 h
msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned
I/ O: _4 `; s# f1 S. x& v& V6 l& X- w
【3】创建Connection对象并连接数据库
4 J [" E. P$ v$ G首先我们需要添加一个指向Connection对象的指针:
0 j: K5 Z% ]5 |9 r_ConnectionPtr m_pConnection;
0 D% a' N- G7 b下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。 " S/ r! Z( e/ q( P! q
6 q' U: r% {- @8 f O# r" N9 }* C& m4 E$ l- H3 _
BOOL CADOTest1Dlg::OnInitDialog()
. d- e: z9 U; R8 i/ O: S! n { 7 x* u7 }4 J Z+ T# b( X
CDialog::OnInitDialog(); % B+ `% f2 O" b: T& k( Y
HRESULT hr; 8 l' F. Q6 e$ _* [ S8 o1 T
try
( p% K2 f$ W$ Z# q/ o9 A { 2 f" I) X$ R/ f2 S
hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象
, q# L6 f" C! ]5 t/ ~; V& Q if(SUCCEEDED(hr))
' ^- C( U: Z/ T1 t { g! }# u1 m5 X6 W6 ?0 v
hr = m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 % X5 W; Q4 A, g! d$ U
///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为 rovider=Microsoft.Jet.OLEDB.3.51; }
# i( Z! o2 L0 R/ v; X }
) ^. {8 @* s0 V& t6 N+ E catch(_com_error e)///捕捉异常
! z. j( x4 Z. m. S {
/ }: K$ o( a1 [7 |0 w6 R7 Z CString errormessage; ; X" P, T4 F3 v
errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); $ P' p8 w6 j9 l7 ~0 S2 I
AfxMessageBox(errormessage);///显示错误信息
) T' |8 U! n$ W+ j( } } d7 I ?# y4 `5 I9 }/ H
! V5 T5 p% |( q% G! R8 O& v在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型
. \ ^2 Z0 G: Z! f1 v! UHRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
: U7 U& x3 d0 \6 C; d3 [$ NConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权, , g' U4 t- b' l% q' ]
Options可以是如下几个常量:
. ^- [5 u4 ^$ A! F: D1 i4 _4 L% p1 iadModeUnknown:缺省。当前的许可权未设置
# G I! e5 h' z4 q, MadModeRead:只读 ' n+ T8 Y9 ^( q* f- B0 {
adModeWrite:只写
2 ?$ Y" z! v* w/ w$ \9 CadModeReadWrite:可以读写 , }+ n% m9 h4 @+ v. \* I
adModeShareDenyRead:阻止其它Connection对象以读权限打开连接
6 N0 W7 H; g/ ]# W, GadModeShareDenyWrite:阻止其它Connection对象以写权限打开连接 0 |3 W1 q9 x/ }% o) ]4 ?
adModeShareExclusive:阻止其它Connection对象打开连接 0 a6 f' ^, S/ w9 R# e
adModeShareDenyNone:允许其它程序或对象以任何权限建立连接
2 T; o d+ y3 }
8 g/ V0 `+ A) P* D, ^我们给出一些常用的连接方式供大家参考: , e# R6 k# ?' m+ Q; }' ~
(1)通过JET数据库引擎对ACCESS2000数据库的连接
0 s% C$ z1 `6 U3 b( _
7 G8 u4 `9 M( C+ ]2 \m_pConnection->Open(" rovider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);
7 @$ ?/ D+ h. I) }. u0 O4 T5 b4 l( Q( ~; C
(2)通过DSN数据源对任何支持ODBC的数据库进行连接:
. m8 y+ m) }& Pm_pConnection->Open("Data Source=adotest;UID=sa WD=;","","",adModeUnknown);
* a$ z9 r4 U: ?$ i( q- X. D9 A+ K; k9 g: m. s" W
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown); ' [# Z! W3 b* p ? K8 S. l
4 U, q j" P( U0 L% `: d' a其中Server是SQL服务器的名称,DATABASE是库的名称
2 q7 M/ q. u( a" J {
+ Z$ p. J7 s. l) F U# G, aConnection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State " j" i/ P, w4 I6 b9 V" S: l
ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 " c5 N+ b9 T6 b3 W$ u2 _
m_pConnection->Open("Data Source=adotest;","","",adModeUnknown); " G" ~6 F9 Y# e7 |/ I
( g7 i5 u0 K% q* r" U8 D' N; f/ {; C2 J3 [
State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如: % W. U) m, E: W
if(m_pConnection->State) 3 T. ~, s& R/ z& n' R: o
m_pConnection->Close(); ///如果已经打开了连接则关闭它
5 b' W' m; v \2 w3 Q( q
/ A& {3 v0 C! `7 r% k; q; |2 ~4 b0 J: }# x' a$ ?
【4】执行SQL命令并取得结果记录集 3 V) Y8 ~4 A9 [/ v% M3 V Y( N
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
7 u i, h- d( G& G% o4 ?并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset"); # G! g; O5 ?: ^9 [1 g+ ~
SQL命令的执行可以采用多种形式,下面我们一进行阐述。
" k% ~' P5 }! ]/ l% s4 o! D O+ t' }* \$ n
(1)利用Connection对象的Execute方法执行SQL命令 7 @& J/ r5 v9 i- v% v6 x
Execute方法的原型如下所示: " q" j/ U& l O' l- \
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一:
6 C, n( X# \) o3 |, x0 p2 hadCmdText:表明CommandText是文本命令
2 ^# M+ {! {& f/ S; E3 b6 v+ |adCmdTable:表明CommandText是一个表名 : o/ |( f9 `% Z# ?: W9 e
adCmdProc:表明CommandText是一个存储过程 / V j; Z7 V% G) T' Q
adCmdUnknown:未知 1 z4 H8 {+ |, O* n" I1 c
: K* j7 e \1 w- w* q% f$ C4 J! zExecute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected;
2 v1 S" g( r, e3 w# {6 l8 r5 | ///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday 9 j( ]; y5 H; `6 f- Y* ~
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); " B C0 g. I2 q( M; d" l" O
///往表格里面添加记录
8 A) f- i9 E- `* o$ m! I m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, nullnullnullnullnullnullnullnullWashingtonnullnullnullnullnullnullnullnull,25,nullnullnullnullnullnullnullnull1970/1/1nullnullnullnullnullnullnullnull)",&RecordsAffected,adCmdText); * f7 [) H# `1 T
///将所有记录old字段的值加一
. R6 x5 ~! T; a m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText); - h: K* x8 @% U$ z
///执行SQL统计命令得到包含记录条数的记录集
5 p8 V1 p' @" p) F% B# |+ n m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
5 o* D! a4 L# g! O r9 S( h _variant_t vIndex = (long)0; : N' \) q: n; V( }
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量 - J6 f$ t7 e% H" s- {
m_pRecordset->Close();///关闭记录集
% `$ D A7 ?5 M% k' O9 r CString message;
% x' _/ O; u3 n; u# g. N5 a; h message.Format("共有%d条记录",vCount.lVal); 3 m& R4 P. ~" { V. c$ A
AfxMessageBox(message);///显示当前记录条数
; }9 \" I8 }% y4 K6 K6 O& a4 L/ {7 g9 R' ^& m9 K2 v& q& n$ l: ?
" f% P9 u% ?4 I2 z7 K# ^(2)利用Command对象来执行SQL命令 9 V/ W/ C! y7 F& o( K
_CommandPtr m_pCommand; ; m* e+ m$ M% o, B. }, ^7 ^/ o$ Y7 t/ e* n
m_pCommand.CreateInstance("ADODB.Command");
' O4 v# `6 N' v _variant_t vNULL;
3 ?6 t1 u5 Z6 O vNULL.vt = VT_ERROR; & `; u% p" l- O7 {& { O
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
' U! |+ h: H6 t# \3 {! H$ V m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它
f! `3 r( z' T5 S7 Q4 V m_pCommand->CommandText = "SELECT * FROM users";///命令字串 $ v+ n. v& G/ }. O
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
% S3 B. c( O3 | V9 ?7 g
$ p w6 O! J! ?; p: O* w4 ~在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。 - G. w5 w- o1 i" F V O' s
9 z9 r3 k4 T+ t4 {+ M7 W, f
2 f. M% h/ _% t
(3)直接用Recordset对象进行查询取得记录集 + s6 i5 v6 u# r. U9 S
例如
( w5 _" `1 \. u6 { K1 Z6 h6 h$ |7 V+ N& T0 ~4 l
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
; r" N& V6 f) F" P- r- D, D2 g! \+ C4 A) U$ @/ d
Open方法的原型是这样的:
z9 k' t, o" B4 [HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
+ P% i+ V/ k _( e8 l其中: . ^% o' K/ @4 J1 W" [
①Source是数据查询字符串
( F$ ?' j) S6 M/ p# h②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
1 K7 Z y2 l+ K9 h! i; A+ Q③CursorType光标类型,它可以是以下值之一,请看这个枚举结构: # V4 c. {/ h. U+ b2 N# v
enum CursorTypeEnum 1 _; ]9 s3 R- T) R3 X5 g$ T2 d0 X
{
, M% E: u7 T- M4 u1 J% R9 kadOpenUnspecified = -1,///不作特别指定
6 k7 ?4 M- O% z$ t4 v2 o1 HadOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
7 ^6 d! O0 I: P+ s SadOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。 9 z: ?2 b' \! n
adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
( w" G1 U. M# X4 ]+ wadOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 - {8 X( A. N% U( I
};
; z! O, p5 c1 H6 s+ U0 D1 z4 n% A7 K4 Y0 |④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
/ A* [ }: f9 h5 Z) [9 S% uenum LockTypeEnum . N2 N9 Z0 S) t6 N) ]
{
# J3 X3 x7 \7 I' QadLockUnspecified = -1,///未指定
# u. Y2 k; d8 t/ {7 `adLockReadOnly = 1,///只读记录集 : G6 }1 i( d V
adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 1 {/ q9 i/ a( v- O2 r* n
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
0 A9 Z- j% Y4 Q+ g H( v# b1 g2 vadLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
, s/ n& P) C# j: T* X3 p( w}; 6 M$ u: d; F# z% u( ? ]9 N/ Y
⑤Options请参考本文中对Connection对象的Execute方法的介绍 ; p/ v9 G ~2 h
R6 @- |/ g+ G2 N
0 [) S4 B, g3 i. j: Q
【5】记录集的遍历、更新
3 ]/ _- K; `% D' w# Y根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday # `; a% G3 |8 ]: m# K! a, \
以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。 - B6 j Q1 M) q" w0 q# k: S
6 a9 r4 E, M1 Z6 @$ q
* A/ m# W: z' Q8 W4 M
_variant_t vUsername,vBirthday,vID,vOld;
3 z7 T' [; H. ]0 A_RecordsetPtr m_pRecordset; . i1 v8 y$ E" |+ S# w# U* D M7 M
m_pRecordset.CreateInstance("ADODB.Recordset");
5 m9 X# J- q, U! W2 Sm_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
( e& C+ M/ V% H/ u/ p7 R5 ?9 Ewhile(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗? # U- k3 ^+ n0 Q' Y0 j" @) H
{
' ]% q5 _" ~( P0 l4 }# ~/ }vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行 $ ?! O/ g- T4 X. C; v2 P) W) p U3 r
vUsername = m_pRecordset->GetCollect("username");///取得username字段的值 $ k- f4 o8 A9 E* ]
vOld = m_pRecordset->GetCollect("old"); " `9 @% P3 F/ }) z7 L* T# [
vBirthday = m_pRecordset->GetCollect("birthday");
9 w8 P4 C8 u0 P$ `+ }$ e///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
# B* r" {2 J+ kif(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
( {; K7 h+ V' a- b0 w2 S D TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); % f! S0 C7 [: L# @4 v! n
m_pRecordset->MoveNext();///移到下一条记录
1 }& u8 {9 |& d$ e}
( g5 z" T x! w! ]" ?# ~2 t: Jm_pRecordset->MoveFirst();///移到首条记录 , K2 ^( A( D0 }( u, p7 K9 R
m_pRecordset->Delete(adAffectCurrent);///删除当前记录
4 ?, N+ c& M# E9 n///添加三条新记录并赋值 : |' l# M* V) |, m- h1 ]
for(int i=0;i<3;i++) ! B3 R5 h& o( [2 Q* S. P
{ ) U- A) [; e: i3 ]5 i) t1 y6 S2 F
m_pRecordset->AddNew();///添加新记录 : A6 k8 t. ?, v2 m
m_pRecordset->PutCollect("ID",_variant_t((long)(i+10))); ! _% {9 E. u4 M' E# t1 p* C! a# m& [
m_pRecordset->PutCollect("username",_variant_t("叶利钦"));
9 c2 r- R. m* z0 N3 O: V) vm_pRecordset->PutCollect("old",_variant_t((long)71)); ) Z2 f) ~% a7 D; ]% O$ y4 q! w
m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
! T8 ]* O- B- L8 Y} & O9 t7 ^: c7 x
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处 0 W7 c3 M5 T7 K
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄
7 C7 I2 }3 B/ _5 P( P* Om_pRecordset->Update();///保存到库中 ( w4 {! `0 _. d* ^- A% e" A6 f
3 X7 b2 M, a$ _/ v( X【6】关闭记录集与连接
& e9 u6 g: {$ Y- V( j, l记录集或连接都可以用Close方法来关闭 . X8 Y3 E4 C2 J7 }
m_pRecordset->Close();///关闭记录集
7 ]: ]; Z. u7 _2 S m_pConnection->Close();///关闭连接
# [# ?1 r0 m, o3 w
2 Q8 R( m* M3 ]7 ^: {$ _ v至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。 8 a, y* \8 o% g1 o
点这里下载示例代码 % ?& S+ T" A: J% \/ G% O9 X
, d! }) Y4 |# ^3 G0 N) ^0 i
后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 |
|