用VB和RDO访问SQL Server

发表于:2007-07-14来源:作者:点击数: 标签:
RDO的发展 远程数据对象 (Remote Data Object ,简称RDO) 是位于 ODBC API 之上的一个对象模型薄层,它依赖 ODBC API、选定的 ODBC 驱动程序以及后端 数据库 引擎实现大部分的智能和功能,因此短小(大约 250 K)、快速、强
RDO的发展
    远程数据对象 (Remote Data Object ,简称RDO) 是位于 ODBC API 之上的一个对象模型薄层,它依赖 ODBC API、选定的 ODBC 驱动程序以及后端数据库引擎实现大部分的智能和功能,因此短小(大约 250 K)、快速、强健。 RDO具备基本的 ODBC 处理方法,所以可以直接执行大多数 ODBC API 函数。RDO包含在VB4.0/VB5.0企业版中,由一个动态连接库MS RDO32.DLL实现。 当VB版本还只是2.0时,从VB访问MS SQL Server的手段只有API函数集(VBSQL/DBLib或 ODBC API)。VB发展到3.0时,其DAO/Jet (Microsoft Aclearcase/" target="_blank" >ccess的数据库引擎)功能非凡,具备面向对象的程序接口,但处理速度尚不尽如人意。另一方面,尽管VBSQL/DBLib、ODBC AP I在速度方面性能很好,但它需要花费很大的精力进行繁琐的编程工作。 当MS SQL Server发展到6.0、VB发展到4.0时,开发了新的对象模块,由此产生了RDO,可以访问SQL Server、Oracle;RDO2.0则是由VB5.0提供的。值得一提的是,它实现了“事件驱动型数据库访问的编程方式”。 要讨论RDO,就必然要谈到DAO。DAO/Jet是为了实现从VB访问Access数据库而开发的程序接口(对象)。RDO是从DAO派生出来的,但两者很大的不同在于其数据库模式。DAO是针对[记录(Records)]和[字段(Fields)],而RDO是作为[行(Rows)]和[列(Columns)]来处理。也就是说DAO 是ISAM模式,RDO是关系模式。此外DAO是访问Access的Jet引擎(Jet是ISAM)的接口,而RDO则是访问ODBC的接口。 可见,RDO是综合了DAO/Jet、VBSQL/DBLib以及ODBC的优点的对象(Object)。需要强调的是,RDO是包裹着ODBC API的一层薄薄的外壳,被设计成在后台(服务器端)有数据库存在的前提下运行,同时也是针对SQL Server和Oracle而特别设计的。 RDO的优势在于它完全被集成在VB之中。此外,直接访问SQL Server存储过程、完全支持T-SQL、T-SQL调试集成在开发环境中、Visual Database Tools的集成化等,也是RDO的长处。

RDO之后是ADO
    Microsoft公司已经明确宣布今后不会对VBSQL/DBLib进行升级,而ODBC API函数一级的编程方式也不为人们所喜爱。RDO自身今后将被以ActiveX技术为基础的ADO(ActiveX Data O bjects)所替代。Visual Basic下一个版本中由哪一个作为标准配置还不很明确,但这一发展趋势已经很清楚。 为什么需要ADO呢?RDO是以ODBC为基础,而ADO则是基于全新的OLE DB技术。ODBC本身是以SQL Server、Oracle等关系数据库作为访问对象;OLE DB则不仅限于此,而将是可以对电子邮件、文本文件、复合文件、数据表等各种各样的数据通过统一的接口进行存取的技术。DAO、RDO当然不会一下子消失,但随着新技术的利用,它们的作用将逐步淡化。

RDO的对象模块图
    要正确地使用RDO,有必要对其对象模块结构加以理解。在RDO的对象和集合中,有很多对数据库的状态和设定进行操作的属性(Property),以及对数据库进行操作的方法(Method)。利用这些,从RDO2.0起就可以开发事件驱动的数据库应用程序。 RDO对象与VB中其他对象的概念相同。与VB用的ActiveX控件(以往称为Custom Control 或OCX、VBX)相似的是,RDO也带有属性和方法;但同Spread、InputMan等普遍应用的Activ eX控件不同的是,RDO没有自己的用户界面,因而可以和VB标准的Timer控件归为同一类。当然也可以将RDO看作调用ODBC API函数,进而对后台数据库操作加以控制的对象。在RDO的属性和方法中,包含了对单个的ODBC API函数以及一连串API函数的调用。
    1. rdoEngine对象 最初调用RDO对象以及RDC(远程数据控件)时,自动生成rdoEngine对象的附带事件(i ncident)。rdoEngine用于对RDO全局属性的参数、选项进行设置,是在RDO的阶层结构内处于最上层的对象,包含了所有的其他对象。 rdoEngine对象与DAO/Jet不同,虽然被多个应用程序共享,但体现rdoEngine对象的设定值的属性却并不共用,而是在各自的应用程序的程序界面中对其分别加以设定。这些设定值对其他使用RDO以及RDC的应用程序没有任何影响。rdoEngine不是集合的要素,而是重新定义的对象,rdoEngine对象不能被追加作成。
    2. rdoEnvironment对象 RDO对象在自动创建rdoEngine对象时,将rdoEnviroment对象的初始值生成并保存为rdo Enviroments(0)。一般情况下,应用程序中不必追加rdoEnvironment对象,大多只需对已有的rdoEnviroments(0)进行操作就可以了。只有在支持一个以上事务(Transaction),需要将用户名和口令信息分别处理的情况下,利用rdoCreateEnvironment方法将特定的用户名和口令值作成新的rdoEnvironment对象。在这个方法中可以指定固有名、用户名和口令,如果所指定的值与rdoEnvironments集合的已经存在的成员名称相同,会产生错误。新建的rdoEnvi ronment对象自动追加在rdoEnvironments集合的最后。调用rdoCreateEnvironment方法时,其name参数可以是长度为0的文字列,这时新的rdoEnvironment对象将不会被追加在rdoEnvi ronments集合之中。

     3.rdoConnection对象 rdoConnection对象用于同SQL Server的连接管理,下面是与SQL Server连接的例子。 第一步用New关键字声明一个rdoConnection对象: Dim Cn as New rdoConnection 由此生成独立的连接对象,这时它还不是rdoConnection集合的成员。在具体连接到SQL Server等之前,有必要设定rdoConnection集合的属性。对此,使用With关键字编程效率会更好。
With CN
    .Connect = “Uid = ; Pwd = ; Database = Pubs; DSN = MyPubsDSN; ”
    '设定Cursor类型
    .CursorDriver = rdUseNone
    '设定登录超时
    .LoginTimeOut = 10

End With
    用RDO与SQL Server连接和断开
    RDO接口没有自动管理同SQL Server的连接和断开的功能,需由程序员自己加以判断。这里需要注意的是对连接和断开时机的管理,因为同时有过多的连接将会造成服务器负载过重。而且,对SQL Server而言,一个连接只能同时支持一个操作,当同时进行记录的读出和更新时,需要分别对其各自的连接加以确认。在一定条件下需要保持连接;反之,当操作完成以后,又需要立即将连接断开。如果能正确地设计好连接和断开的时刻,就有可能确保扩展更多的用户数(客户/服务器开发环境中,与数据库项的可扩展性一样,客户数扩张的可能性也非常重要)。 由于RDO是基于ODBC,它通过ODBC的数据源名(DSN: Data Source Name)与SQL Server相连。DSN的信息可以基于文件,也可以在连接时通过传递的参数指定。不过,一般来说,不推荐基于文件的DSN,因为一旦DSN文件被破坏,或者被误删除,设定起来就非常麻烦。通过 DSN对SQL Server的连接方式一般有以下几种: ?
    查询ODBC的登录信息中是否存在有效的DSN:可以通过ODBC API的SQLDataSources函数取得DSN条目的列表,不过这种办法实用性不大。 ?
    用rdoRegisterDataSource函数生成新的DSN:虽然多少有一些麻烦,但比查询ODBC 登录信息要好一些。 ?
    与已有的DSN相连:需要手工设置DSN,而且安装应用程序之后还需个别地设定DSN,对用户来说不是很友好的解决方法。 ?
    从应用程序中自动启动控制面板的ODBC管理applet,向用户说明DSN的设定方法:有的应用程序就是用这种方法进行设定,但仍不是很实用的办法。 ?
    将SQL Server名保存在Windows Registry或INI文件,在连接时对rdoConnectio ns对象的属性作如下设置:在初次运行应用程序,或者找不到指定的SQL Server名的情况下,要求用户进行输入,并设置在Windows Registry或?INI文件中。通常,SQL Server名不会改变;当连接出现错误时,可以像初次运行应用程序时那样,为用户提供指定SQL Server 名的对话框,这种做法最为理想。 ?
    利用ODBC3.0驱动支持的功能在连接时实际指定DSN文件:作安装程序时包含这个DS N信息的文件,随同程序一起安装。
    此外还有其他一些DSN的连接方法,这里不作一一描述。 这些方法中具体哪一个最实用,需根据各种应用程序的安全性要求、安装方法等进行考虑加以选择,一般是组合上述方法的其中几种来编程。也就是说,用DSN文件的方式来作实际上的连接,而同时采用对DSN文件是否存在进行检查、不存在时要求用户输入SQLServer 名、用户名、口令并即时自动生成的方法。 实际的连接除了RDO 1.0的OpenConnection方法以外,也可以用RDO 2.0中新增加的Esta blishConnection方法,我们将在下期中介绍这些方法。此外,下面我们还将通过实际代码向大家继续介绍经DSN对SQL Server的连接方式、数据的读取、追加、更新、删除等内容。

实例
    在本系列文章的上篇刊文中,我们介绍了RDO的发展、优势、对象模块、趋势,以及通过RDO与SQL Server的连接和断开等内容,本期将通过实例来介绍RDO的应用。
    OpenConnection方法的调用方式如下:
    [调用方式:OpenConnection(dsName, Prompt, ReadOnly, Connect)]
其中参数说明如下:
?  dsName,指定登录在系统中的DSN条目。当用Connect参数指定DSN值时,dsName必须为“”(长度为0的文字列)。
?  Prompt,当设置为rdDriverPrompt常数时,可以在不能与SQL Server连接的情况下,激活ODBC设置对话框来设定DSN。只是最好不要让一般用户随意更改这个设定。通常情况下,正确的处理方法是设定为rdDriverNoPrompt,利用Error处理程序编程进行处理。
?  ReadOnly,若需要通过连接对数据进行更新时设定为False。若没有数据更新的必要则设为True,这时因为ODBC驱动没有对数据更新进行管理的需要,可以提高程序效率。
?  Connect,向ODBC驱动管理器传递所有的ODBC连接参数。可以省略dsName,只通过Co nnect参数进行包括用户名、口令、缺省数据库、DSN(此时dsName参数的值无效)等全部参数值的传递。
    下面是OpenConnection方法的一个实例。
设定的DSN为MyDSN:
Dim Cn As rdoConnection
Dim En As rdoEnvironment
Dim Conn As String
Conn = “DSN = MyDSN; UID = Jacob;” & “PWD = 123456; DATABASE = MyDb;”
Set Cn = En.OpenConnection(“”, rdDriverPrompt, False, Conn)
在这个例子中,dsName是空 “”(长度为0的文字列)。DSN情报从Conn参数中所含的DSN = MyDSN取得。
OpenConnection方法中也可以通过变量来进行参数传递:
[变量名:=值]
Set Cn = En.OpenConnection(Prompt := rdDriverPrompt, ReadOnly := False, Conn ect := Cnn)
虽然这多少有一些多余的代码,但毫无疑问会使程序维护工作容易得多。
EstablishConnection方法
EstablishConnection方法的调用方式如下:
调用方式:EstablishConnection(Prompt, ReadOnly, Connect)
本文前面对独立(stand alone)的连接对象(rdoConnection)作了说明,EstablishCon nection方法可以用于这种情况。EstablishConnection方法同OpenConnection方法很相似,被用于停止状态或独立的rdoConnection对象。
这里以独立的rdoConnection对象为例说明与SQL Server的连接。
Public WithEvents Eng As rdoEngine
Public WithEvents Cn As rdoConnection
 
Private Sub Form_Load()
    Set Eng = New rdoEngine
    Set Cn = New rdoConnection
    With Cn
        .Connect = “UID = ; PWD = ;” & “DATABASE = pubs; DSN = biblio”
        .LoginTimeout = 5
        .EstablishConnection rdoDriverNoPromt, True, rdAsyncEnable
    End With
End Sub
    在这个例子中,Form_Load函数对rdoEngine和rdoConnection对象进行初始化。这里有一点需要注意,rdoConnection对象是处于独立的状态之下,即使是处于未连接状态也可以设置属性的值。
    接下来是rdoConnect对象的事件处理程序。从RDO 2.0起可以实现异步方式(rdAsyncEn able),EstablishConnection就设定为该值。在异步状态下,不必等待与数据库的连接,程序可以迅速从Form_Load函数中退出。
    然后是BeforeConnect事件,该处理在与数据库的连接开始以前被激发,此时不能进行有关终止连接的操作:
Private Sub Cn_BeforeConnect(Co.netString As String, Prompt As Variant)
    MsgBox “正在连接” & ConnectString, vbOKOnly, “连接前”
End Sub
    连接完成之后的事件处理:
Private Sub Cn_Connect(ByVal ErrorOccurred As Boolean)
    Dim M As String
    If ErrorOccurred Then
        For Each er In rdoErrors
            M = M & er & vbCrLf & M
        Next
        MsgBox “连接失败” & vbCrLf & M
    Else
        MsgBox “连接成功”
        '这是确认连接状态的测试代码
        Cn.Excute “use pubs”
    End If

End Sub
    RDO连接处理结束后,在该事件中确认连接成功与否。连接成功的情况下ErrorOccurred 返回False,失败时为True,由此可以对rdoErrors集合进行检测:
Private Sub Eng_InfoMessage()
    For Each er In rdoErrors
        Debug.Print er
    Next
    RdoErrors.Clear
End Sub
    不能与SQL Server连接的原因多种多样,有可能是由于对数据库的访问权限、网络连接问题、数据库表的信息错误、SQL Server同时连接的许可数、资源不足等等,具体情况需要与网络管理员商量。
    断开连接的操作非常简单,但又很重要,因为RDO不提供自动断开的功能。
Cn.Close
Set Cn = Nothing '释放对象所占的内存资源
En.Close
Set En = Nothing '释放对象所占的内存资源
    VB是对象语言,Form、ActiveX控件也都是对象。使用对象后必须养成将对象设为Nothi ng把它从内存中释放的编程习惯。这样可以预防很多不可预测错误,往往程序中发生意义不明的错误时,其原因就在于此。


数据的取得

    与数据库连接成功之后,接下来就是取得数据。一般用OpenResultset方法取得数据,这里首先需理解数据库中与此有关的[游标]概念。
    一言以蔽之,游标[cursor]就是指向依据一定的条件从数据库中抽取的数据的多个指针。也就是说游标是用作指向由数据库返回的数据的方法。
RDO 支持几种不同的游标库,其中每一种都有其特定的作用:向前滚动型的结果集(rdOpenForwardOnly-缺省值)、静态滚动型结果集(rdOpenStatic)、可滚动的查询结果集(rdOpenKeyset)和动态可滚动的查询结果集(rdoOpenDynamic)。在使用RDC的情况下,游标的值设定为ResultsetType属性;在使用RDO的情况下,通过OpenResultset方法的Type参数进行设定。游标又分为[服务器端游标]和[客户端游标],这需要根据程序的性质、处理内容的不同来选择适当的方式。也有不使用游标的情况,比如当只进行数据读取时,使用rdUseNone选项更为合适。
    关于是否使用游标、使用何种游标,需考虑下面一些情况:
 * 需要读取多少行数据?要读取全部或只是几行数据?
  * 是否等待游标的创建完成?对于用户来说等待时间是否在允许范围之内?
  * 用来保存已创建游标的系统资源(内存容量),在用户端或服务器端是否充足?
  * 从服务器端返回的结果怎样读取?有必要从当前行随意移动,还是从最初到最后顺序读取为好?
  * 游标的成员关系怎样定义?
  * 数据如何更新?数据有没有更新的必要?是通过Execute方法将游标的数据进行更新,或是由存储过程更新?
    关于游标的详细介绍,请参照Visual Basic Books Online以及SQL Server用户手册。这里介绍从RDO对象读取数据的几种方法:
  * rdoResultset对象,这是RDO的基本游标对象。与DAO的Recordset对象相似,可应用于各种游标以及无游标的场合。可以通过rdoConnect对象或rdoQuery对象的OpenResultset方法创建rdoResultset对象。
  * rdoQuery对象,与DAO的QueryDef相似,在进行一次性查询时使用,用于取代已经过时的由rdoConnection对象的CreatePreparedStatement方法创建的rdoPreparedStatement对象。rdoQuery对象直接调用ODBC的SQLPrepare。
  * UserConnection对象,在VB5.0以及RDO 2.0中新增加的对象,这里不作详细阐述。它是一个非常优秀的对象,使RDO使用起来更加容易。
  * rdoTable对象,是rdoTables集合的成员,用来显示SQL Sever上的一个表的内容。
编程中通常运用前3种方法中的某一种,而第4种一般不太用。
    不论使用哪一种方法,都有必要给出SQL语句。在绝大多数的情况下,SQL语句是针对SQL Server,从哪个表以何种条件读取哪一行的数据,如(SELECT * FROM table1 WHERE field1 = “condition”)。运行存储过程时用Name参数指定存储过程,程序能自动判别是RDO 2.0的还是存储过程的SQL语句。在以存储过程为核心的情况下,利用UserConnection将存储过程作为方法来处理最为妥当。
   下面给出一个运行rdoConnection对象Cn的OpenResultset方法的例子。
Dim Rs As Resultset
Set Rs = Cn.OpenResultset(name:=“SELECT * FROM Authors WHERE Year_Born=1966”)
If Rs.RowCount > 0 Then
    MsgBox Rs.RowCount & “条记录取得完毕。”
Else
    MsgBox “没有取得任何记录。”
End If
    这个例子中用了name:=,OpenResultset方法,除了name参数以外,也可以使用lock、 locktype、option等。下面是从Resultset中将数据读取到ListBox中的方法:
Do Until Rs.EOF
    List1.AddItem Rs(“au_lname”)
    Rs.MoveNext
Loop
    存储过程虽然不在本文讨论的范围内,这里也简单地介绍一下。存储过程基本上有以下4种类型:
{call myStoreProcedure} ' 没有参数的存储过程
{call myStoreProcedure(?)} ' 单一的输入或输出参数
{? = call myStoreProcedure(?)} ' 单一参数、有返回值
{? = call myStoreProcedure(?, ?)} ' 多个参数、有返回值
    下面演示一个运行存储过程的例子:
sp_GetVendorCount参数是名称的条件,其返回值为符合该条件的记录数。
Dim CPw As rdoQuery
Dim sSQL As String
SSQl = “{? = call sp_GetVendorCount(?)}”
    这里“名称的条件”是输入参数,由ODBC驱动器进行自动识别。使用rdoParameters(n).Direction可以对返回值(rdParamReturnValue)、输入参数 (rdParamInput) 、输出参数(rdParamOutput)和输入输出参数(rdParamInputOutput)加以控制。但通常ODBC都会读入存储过程的定义式,并加以正确识别,所以绝大多数的情况下不必使用这个参数。
Dim CPw = Cn.CreateQuery(“GetVendorCount”, sSQL)
    代码生成名为GetVendorCount的rdoQuery对象,并将rdoQuery对象自动增加到rdoQueries集合中,以后可以重复使用。
    现在将CPw对象的第一个参数指定为返回值:
CPw.rdoParameters(0).Direction = rdParamReturnValue
    最后由Execute方法运行:
CPw.Execute
    返回行的查询(存储过程中包含一个以上的SELECT)时,可使用OpenResultset方法。运行后,可通过rdoParameters集合取得返回值:
If CPw.rdoParameters(0) > 0 Then
    MsgBox CPw.rdoParameters & “数据取得成功”
Else
    MsgBox “数据读取失败”
End If


数据的追加、更新、删除
    对SQL语句已经有一定了解的读者,应该比较熟悉INSERT、UPDATE、DELETE等语句。对于rdoConnection对象,虽然可以在OpenResultset的Name参数中直接代入SQL语句,用Execute方法运行,但没有充分利用RDO对象的长处。在rdoResultset中有AddNew、Edit、Update、Delete、MoveNext、MovePrevious、MoveFirst、MoveLast方法,与DAO/Jet相似,用起来非常便利。
    下面在先前的Resultset的例子中追加记录:
Rs.AddNew
Rs(“au_id”) = “111-46-1992”
Rs(“au_lname”) = “Takenami”
RS(“au_fname”) = “Teruo”
'设定所有的field,通常调用Call SetField,在别的模块中设定
Rs.Update
    与SQL语句中的INSERT相比起来,这种方法非常简单,而且代码可读性好。记录的更新方法如下,与追加相似,所不同的只有最初的Edit方法:
Rs.Edit
Rs(“au_lname”) = “Takenami”
RS(“au_fname”) = “Teruo”
'设定所有的field,通常调用Call SetField,在别的模块中设定
Rs.Update
    在实际应用中,字段的更新放在别的模块中,便于从AddNew、Edit两种处理中都可以进行调用。
    使用Delete方法删除记录时,当前行被删除。当前行可以通过MoveNext、MovePrevious等Move方法以及Bookmark属性设定。
Rs.Delete '删除当前记录
    这里需要注意的是当前记录被删除之后记录指针的位置。Delete执行后,记录指针仍然指向已被删除的记录,也就是空的记录,对这个空记录进行读写操作会产生错误。所以通常在Delete之后应立即执行ReQuery或MoveNext操作:
Rs.ReQuery 或 Rs.MoveNext
    以上简单地说明了数据的增加、更新、删除方法,根据数据库的模式的不同,增加、更新会变得非常复杂。另外,由于数据表的原因,有时会使得删除操作变得复杂。通常与数据表的构造、相关性有关的处理,为了使客户端的代码尽可能简洁,应在SQL Server上创建触发器。有关触发器的内容已超出了本文讨论的范围,这里不详细说明。本文介绍的只是一些基本的操作方法,RDO数据处理功能不仅限于此,读者可在实际开发中进一步领会。


总结
    RDO是开发数据库应用程序功能强大的对象方法,要真正做到应用自如,需要付出很大努力。本文描绘了RDO基本的构成、功能、编程方式,希望读者由此对数据库编程方式以及RDO的使用有更为充分的了解。如果需要进一步研究,建议可以从以下几方面入手:
  * SQL Server(或Oracle)的功能,特别是存储过程、View、触发器、安全模式等;
  * 数据库设计基础;
  * SQL语句;
  * Visual Basic的对象概念。
    这些粗看起来与RDO没有什么联系,但实际上有助于对RDO的结构、原理等基本技术的理解。换而言之,学习数据库编程的基本内容为大前提,RDO或者ADO的应用不过是访问数据库的一种手段而已。

原文转自:http://www.ltesting.net