Ken Lassesen
Microsoft Developer Network Technology Group
September 11, 1995
Click to open or copy the files in the IntroRDO sample application for this technical article.
This technical article shows an experienced Visual Basic® 3.0 developer how to exploit the Remote Data Object (RDO) available in Visual Basic 4.0 Enterprise Edition. This article allows the developer to move existing applications from using the DAO to using the RDO quickly.
Note The RDO is only available in 32-bit Visual Basic. The client must run Windows® 95 or Windows NT® version 3.51 or later.
The Remote Data Object (RDO) and Remote Data Control (RDC) are examples of yet other data access methods. The RDC binds the RDO to a control in much the same way that the Remote Data Control binds the Data Access Object (DAO) to a control. The RDO is a thin layer of code placed on top of Open Database Connectivity (ODBC) with some special features like server-side cursors for accessing Microsoft® SQL Server version 6.0. When I first heard about the RDO, I remarked, "O lovely data! How do I access thee? Let me count the ways—DAO, data control, DB-Library™, Jet, VBSQL, ODBC, SQL-DMO . . . ." The number of data access methods available is awesome to some, irritating for the indecisive, and terrifying to the timid. Because many of you may not know what all of these terms, acronyms, and initialisms are, I have listed them in Table 1.
Table 1. Terms, Acronyms, and Initialisms Used for Data Access Methods
Term/Acronym/ Initialism |
Description |
DAO | Data Access Object |
Data Control | Data Control |
DBLib | DB-Library |
Jet | Microsoft Jet Database Engine |
ODBC | Open Database Connectivity |
RDO | Remote Data Object |
SQL-DMO | SQL Distributed Management Objects |
VBSQL | Visual Basic Library for SQL Server (VBSQL), an implementation of the DB-Library application programming interface (API) |
RDC | Remote Data Control |
Each data access method has strengths and weaknesses. Although there are many factors to consider, my own opinion of how the common methods relate to each other is shown in Figure 1. Actual performance can vary greatly depending on environmental factors—database type, network, amount of traffic, configuration, so take it all with several grains of salt.
Figure 1. Personal evaluation of common data access methods
To better understand the processes involved, you should examine Figure 2, where I show the layers involved in each of the object-oriented methods.
Figure 2. The layers used in common data access methods to communicate with Microsoft SQL Server
The two most critical factors in successful data access are the time it takes to code the application (both development and maintenance coding) and the speed of retrieving data from the database server. In my opinion, the RDO provides the best mix of those two factors—and the RDO is even better because it requires very few resources from the workstation.
The RDO is an alternative that in many scenarios performs better than the DAO. Some future version of the DAO should include all of the positive features of the RDO, but if you want those features today, you must use the RDO. The RDO's developers designed it explicitly for accessing data on remote servers like Microsoft SQL Server version 6.0 or ORACLE®. The DAO's developers designed it for accessing data from both remote servers and local workstations, for example, from xBase files, Microsoft Access files, and Paradox™ files.
The RDO is a bad choice for reading data from a Microsoft Access database on your local drive because it will go through all of the layers that the DAO uses, as well as its own layer. The RDO is an excellent choice for reading data from Microsoft SQL Server 6.0 because the RDO developers put extra effort into tuning its performance with this database. Think of the RDO as a replacement for making DB-Library™ application programming interface (API) calls or ODBC API calls—it's a way to get performance without spending weeks coding API calls. I repeat, if you are writing client/server applications with Microsoft SQL Server, the RDO is my first choice and I am willing to debate anyone who suggests a different approach.
The greatest advantage of the RDO is that it does not use the Jet engine. The Jet engine consumes a large amount of resources and adds a lot of overhead. The RDO uses a thin code layer over the ODBC layer and the driver manager that establishes connections to the database. This thin layer appears very similar to the DAO but without all of the resources required by Jet on the client PC. This small memory footprint results in a huge drop in the amount of memory required and reduces the code swapped to the hard drive on some workstations. The reduction of disk swapping can produce very significant performance gains on low-end systems, such as a 386SX-25 with 4 MB or less of RAM.
The biggest drawback of the RDO occurs when the developer must convert an application from the DAO code to the RDO code. With a later version of Visual Basic, the developer may have to convert the application back to using the DAO. This drawback is minor—you can use a Visual Basic add-in to do most of the work. I describe add-ins in my article "Building Add-Ins for Visual Basic 4.0."
What does the developer gain or lose when she or he moves from using the RDO to using the DAO? Table 2 shows my opinion of the most significant strengths and weaknesses of each.
Table 2. Comparison of DAO and RDO Features
Feature | DAO | RDO |
Resources on workstation | High (Minimal PC: 486DX-66, 12 MB) |
Low (Minimal PC: 386DX-33, 6 MB) |
Access to server-side cursors | No | Yes |
Code is forward-compatible | Yes | No |
Asynchronous queries | No | Yes |
Multiple result set processing | No (See Note) | Yes |
Stored procedure support with output parameters and return values | No | Yes |
Note The DAO can technically do multiple result sets; however, the code is ugly. See Bill Vaughn's book, A Hitchhiker's Guide to Visual Basic 4 and SQL Server 6, for more information.
In the following sections, I will take an example of a typical Visual Basic 3.0 routine written using the DAO and then show the same routine written using the RDO. Most Visual Basic developers are very familiar with the DAO and will try to code the RDO as if it were the DAO. Unfortunately, there are some significant coding differences between these two products that can become confusing. The examples should guide you through these difficulties.
The RDO talks to the database at a lower level than the DAO, so you will need to know more about concepts like connections to take full advantage of the RDO. The lower level gives you more control but with the usual cost of having more to consider. One sweet thing about using connections is that you can set different property values for each one. For example, on a former project, I was using the DAO to work concurrently with Microsoft SQL Servers located in Ireland and in the next office. The DAO forced me to use one set of time-out values for both servers, but with the RDO I can have different time-out values for each server.
To illustrate the changes, I will use a generic procedure that retrieves data from a database and displays the data in a control. I will start with a Visual Basic 3.0 version of this procedure, which I will convert to a Visual Basic 4.0 DAO procedure to illustrate style changes between versions of Visual Basic. By convert I do not mean that Visual Basic 4.0 requires you to port the procedure for it to work; it will work fine as it is. I mean that I will change the procedure to what I think the Visual Basic 4.0 procedure should look like, which is different from a Visual Basic 3.0 procedure—in other words, to a great procedure instead of one that simply works.
Next, I will convert the procedure from a DAO procedure to an RDO procedure. The RDO does not offer as many options as the DAO, but when applicable, the RDO often performs significantly better.
This sample procedure is a function that fills any list-box type of control from an SQL query statement. The procedure returns the number of rows added to the control. The sample below shows a typical Visual Basic 3.0 style of coding. (Note that some extra returns have been added—for example, in the first Function statement—to make the code readable on your screen.)
Function VB3_FillLstFromSQL(Lst As Control, ByVal DBName$, ByVal SQLQuery$,
ByVal DBConnect$, ByVal ReadOnly%, ByVal RSType%, ByVal RSOptions%)
On Error GoTo VB3_Error
Dim DB As Database
Dim RS As Recordset
Dim nRecords&
VB3Consistent_DAOOpenRecordset DBName$, RSType%, RSOptions, DBConnect$
If ReadOnly% <> 0 Then ReadOnly% = True 'Validate
Lst.Clear
Set DB = OpenDatabase(DBName$, False, ReadOnly%, DBConnect$)
Set RS = DB.OpenRecordset(SQLQuery$, RSType%, RSOptions%)
While Not RS.EOF
Lst.AddItem (RS(0))
RS.MoveNext
nRecords& = nRecords& + 1
Wend
VB3_Exit:
VB3_FillLstFromSQL = nRecords& 'Return # added to Lst
On Error GoTo 0
Exit Function
VB3_Error: 'Display message and exit procedure
MsgBox Error$, vbWarning, "Error:" & Err
Resume VB3_Exit
End Function
So what does the procedure do? First, it corrects any inconsistency in the passed parameters by calling VB3Consistent_DAOOpenRecordset. The details are in the IntroRDO sample code. The procedure opens the record set and adds the records. The (RS(0)) converts the first column, which should be the only column, to a string. If the control displays multiple columns, the SQL statement returns a single field that formats these columns as a single field; for example:
Select SSN, Lname, Fname From Employees
I can submit this query as follows:
Select SSN + CHR$(9)+Lname+CHR$(9)+Fname From Employees
This query can significantly speed the time it takes to fill the control and can shorten the length of time during which there is a read-lock on the table. If any errors occur, the procedure displays the error and then exits. I reset the error-handling routine when I exit the procedure.
Visual Basic 4.0 supports several new features, shown in Table 3, that can dramatically change the ease of debugging, the appearance, and the performance of your code. I use the new features because I find my code to be easier to understand and to extend when I do so. The changes are sufficiently significant for me to get up in the pulpit for a short sermon. So just call me Pastor Ken as I show you my favorite new Visual Basic features in Table 3.
Table 3. Recommended New Features to Use in Visual Basic 4.0
Feature | Description |
Support for named arguments | Allows arguments to be more meaningful. Instead of:
the code reads:
|
Support for optional arguments | Allows arguments to show only essential information and exceptions while supporting more generic procedures. |
Supports line continuation | Allows better formatting and commenting of code. |
Support for references | Allows for better identification of objects and extends the product. |
The Visual Basic 3.0 procedure above in the "Visual Basic 3.0 DAO Procedure" section takes on a very different look when written in Visual Basic 4.0. The change of appearance is due to the use of named arguments and line continuation. The line continuation characters allow long statements to wrap several lines, which makes for better formatting and readability. A programmer can use named arguments to arrange arguments in a different order—for example, from most significant to least significant. Here is the procedure from the previous section written in Visual Basic 4.0:
Function DAO_FillLstFromSQL( _
LstControl As Control, _
ByVal DBName$, _
ByVal SQLQuery$, _
Optional DBConnect, _
Optional ReadOnly, _
Optional RSType, _
Optional Options, _
Optional Exclusive _
)
On Error GoTo DAO_Error
Dim DE As New dao.DBEngine
Dim DB As dao.Database
Dim RS As dao.Recordset
Dim nRecords&
VB4Consistent_DAOOpenRecordset _
DBConnect:=DBConnect, _
RSType:=RSType, _
Options:=Options
If IsMissing(ReadOnly) Then
ReadOnly = True 'Validate
ElseIf Not IsNumeric(ReadOnly) Then
ReadOnly = True
ElseIf ReadOnly <> 0 Then
ReadOnly = True
End If
LstControl.Clear
'I prefer to see Connect and name together:
Set DB = DE.OpenDatabase( _
Name:=DBName$, _
Connect:=DBConnect, _
Exclusive:=Exclusive, _
ReadOnly:=dbReadOnly _
)
Set RS = DB.OpenRecordset( _
Name:=SQLQuery$, _
Type:=RSType, _
Options:=Options _
)
While Not RS.EOF
LstControl.AddItem (RS(0))
RS.MoveNext
nRecords& = nRecords& + 1
Wend
DAO_Exit:
DAO_FillLstFromSQL = nRecords& 'Return # added to Lst
On Error GoTo 0
Exit Function
DAO_Error: 'Display message and exit procedure
MsgBox Error$, vbExclamation, "Error:" & Err
Resume DAO_Exit
End Function
The use of optional arguments with named arguments allows the developer to use one procedure in many different ways. This reduces the number of procedures in an application. For example, the VB4Consistent_DAOOpenRecordset procedure uses all optional arguments. This procedure determines the tests and corrections by the arguments passed. The procedure uses the IsMissing function to test for passed arguments. For details on how I implemented it, examine the code in the IntroRDO sample that accompanies this article.
The developer may extend Visual Basic 4.0 very easily by using references, but the use of references requires some caution. Different references may use the same object name, property name, or method name. This may result in names becoming unexpectedly ambiguous. I prevent this ambiguity by always fully qualifying objects. The DAO reference qualifies the Database and Recordset types. This may be overkill, but it's worth it to prevent other people from creating object models with the same name (for example, "Database"). Other database manufacturers may imitate the DAO model to make it easier for developers to use (and force you to add the qualifiers later). I prefer to solve problems before they arrive and have placed the rule "Always fully qualify all objects" beside the rule "Always use Options Explicit" because the reasons for doing so are very similar.
Calling the DAO_FillLstFromSQL procedure can also appear to be very different in Visual Basic 4.0 than it is in Visual Basic 3.0. Visual Basic 3.0 requires the procedure call to specify all arguments. A misplaced argument can cause one to spend long hours debugging a program—which actually happened to me in preparing this article. Here is an example of an equivalent Visual Basic 3.0 procedure call (we added the return to make the line visible on your screen):
VB3_FillLstFromSQL lstListBox, (DBName), txtSQLQuery, txtDBConnect,
dbReadOnly, ThisRSType(), ThisRSOptions()
Visual Basic 4.0 requires only the arguments that are not the default values, resulting in less muddled code. The naming of variables and named arguments can also clarify code. The Visual Basic 4.0 procedure call below is easier to understand than the call shown above.
DAO_FillLstFromSQL _
LstControl:=lstListBox, _
DBName:=txtDBName.Text, _
DBConnect:=txtDBConnect.Text, _
SQLQuery:=txtSQLQuery.Text
Having changed my code to the new Visual Basic 4.0 style, I will now convert this procedure from using the DAO to using the RDO.
The RDO code is very similar in overall pattern to that of the DAO, although the designers changed some names to confuse the innocent. First I'll show you the RDO code and then explain what has changed and why.
Function RDO_FillLstFromSQL( _
LstControl As Control, _
ByVal DBName$, _
ByVal SQLQuery$, _
Optional DBConnect, _
Optional ReadOnly, _
Optional RSType, _
Optional Options, _
Optional LockType, Optional Prompt _
) 'NOTE This is the line continuation limit
On Error GoTo rDO_Error
Dim DB As rdo.rdoConnection
Dim Env As rdo.rdoEnvironment
Dim RS As rdo.rdoResultset
Dim nRecords&
LstControl.Clear
Consistent_RDOOpenRecordset _
DBConnect:=DBConnect, _
RSType:=RSType, _
Options:=Options
If IsMissing(ReadOnly) Then
ReadOnly = True 'Validate
ElseIf Not IsNumeric(ReadOnly) Then
ReadOnly = True
ElseIf ReadOnly <> 0 Then
ReadOnly = True
End If
Set DB = rdoEngine.rdoEnvironments(0).OpenConnection( _
dsName:=DBName$, _
Prompt:=Prompt, _
ReadOnly:=ReadOnly, _
Connect:=DBConnect _
)
Set RS = DB.OpenResultset( _
Name:=SQLQuery, _
Type:=RSType, _
LockType:=LockType, _
Option:=Options _
)
While Not RS.EOF
LstControl.AddItem (RS(0))
RS.MoveNext
nRecords& = nRecords& + 1
Wend
rDO_Exit:
RDO_FillLstFromSQL = nRecords& 'Return # added to Lst
On Error GoTo 0
Exit Function
rDO_Error: 'Display message and exit procedure
MsgBox Error$, vbExclamation, "Error:" & Err
Resume rDO_Exit
End Function
The first thing you will notice about the code above is that the procedure declaration drops one argument and adds two more arguments. All of these procedure arguments are intentionally optional so that I can switch easily from the DAO to the RDO if I do not use them. For simplicity, I omitted the asynchronous code from the above example; see the IntroRDO sample for how to handle asynchronous result sets.
The DAO Exclusive argument grants one user-exclusive control over the database (usually for performance reasons), which does not fit the RDO scenario, in which multiple users can access the database.
The RDO LockType argument determines the type of locking on the server. The common RDO.LockType can be one of these three types: optimistic concurrency based on row values, optimistic concurrency based on row ID, or pessimistic concurrency. Changing the type of lock concurrency can result in significant performance gains on database servers with heavy loads by reducing the number and duration of locks.
The RDO Prompt argument grants more flexibility in how the ODBC connection occurs. Next, I will examine the differences between RDO.rdoConnection and DAO.OpenDatabase, between RDO.rdoResultset and DAO.Recordset, and between RDO.rdoPreparedStatement and DAO.QueryDef.
Before getting into the details, I would suggest printing copies of the DAO and RDO maps for reference. You will find these maps in my articles "Mapping the Data Access Object: DAO 3.0" and "Mapping the Remote Data Object: RDO 1.0." Your first impression will be of how small an object RDO is! This smallness is because the RDO developers aimed it for a client/server application. They assumed that the developer would use other tools, such as SQL-DMO, to define tables, triggers, user log-ins, and views. In short, the RDO provides only an application interface and excludes parts of a development interface.
The RDO.rdoConnection object provides functionality similar to that of the DAO.Database object. The major difference between these objects is the ability of the DAO to directly open a physical database file; the RDO cannot directly open a physical database file. Both of these functions share the ODBC Connect property. The RDO.rdoConnection requires an environment that is a new layer that the DAO does not have. Usually the default environment is adequate unless you wish to do a single transaction against multiple servers or need to sign on as several different users concurrently.
Another big difference between these two objects is that the RDO.rdoConnection object represents a physical connection to the server. When you call the RDO.Close method on the RDO.rdoConnection object, the RDO calls SQLDisconnect and drops the connection immediately. This is different from the DAO, where Jet will cache connections for reuse that can cause complex problems if one of these cached connections dies unexpectedly.
Speaking of connections, if you do not close the connection when using the RDO, the RDO adds the result set to the RDO.rdoResultsets collection. You can locate a result set in this collection either by ordinal number or by name (the first 255 characters of the SQL query). This ability to locate by name is cool because it lets you toss away lots of heritage code! I can locate an existing result set by name and let the RDO find it for me instead of my code tracking the result set.
These objects not only have names that sound alike; they have almost the same set of arguments and very similar behaviors. To understand the difference, think of a DAO.Recordset as a set of records: Each member of the set is a direct pointer to physical records in the database (not always true, but close). An RDO.rdoResultset object is the set of values that these records' fields returned. If you are reading data, there is little difference. If you are updating data, things are different. The DAO places a lock on the record at the moment you execute the DAO.Edit method. This lock remains on the record until the application executes DAO.Update. The RDO does not place a lock on the record. The RDO places a copy in a buffer when you execute RDO.Edit. When the RDO updates the record, the RDO compares the copy in the buffer to the copy existing on the database server. If the copy is unchanged, the RDO locks the record and updates the record on the server. This difference is why the RDO has the LockType property on a result set. This is a very rough description of the general difference. For a technically correct description, you need to read the documentation for each of the many options very carefully.
The bottom line is that the RDO can often allow heavier loads on a server than the DAO because of the differences in locking mechanisms.
These objects provide similar base functionality, but the RDO adds the extra functionality that was available only from the ODBC API or the VBSQL API. Both support the Execute method, but only RDO.Execute allows asynchronous execution of the query. (Similarly, RDO.OpenResultset can execute asynchronously, while DAO.OpenRecordset executes synchronously.) Asynchronous execution is more challenging because it means that the developer must code for reentrancy. The RDO.rdoPreparedStatement shines with stored procedures. Unlike the DAO, the RDO allows you to obtain output parameters and result codes from stored procedures. Similarly, you can set the severity level for fatal errors from the database. Another feature of RDO.rdoPreparedStatement is that the rdoParameter collection allows execution of stored procedures with different parameters without rebuilding the string by using concatenation.
In short, all of the ODBC and VBSQL features and performance that required you to code direct API calls is now available in the RDO. You do not have to create and debug a 32-bit version of the API calls when you move to Visual Basic 4.0, nor maintain both 16-bit and 32-bit versions of API code.
The RDO exposes three important handles to the ODBC driver for those programmers that need to make a few API calls in an application to feel fulfilled. The RDO.rdoEnvironment object exposes the ODBC environment handle, RDO.hEnv. The RDO.rdoConnection object exposes the ODBC connection handle, RDO.hDbc. Both RDO.rdoPreparedStatement and RDO.rdoResultset objects expose the ODBC statement handle, RDO.hStmt. This is one of the strengths of the RDO—the ability to code with the simplicity of the DAO, while providing easy access to the ODBC layer when needed.
At the beginning of this article, I gave preference to the RDO over the DAO, but I qualified it by stating that the environment can affect performance. The IntroRDO sample application allows you to time various configurations using the DAO and the RDO to determine which configuration is best for you. The DAO used in Visual Basic 4.0 is significantly faster than in Visual Basic 3.0, and the RDO is faster than either of them.
IntroRDO may teach you a lot about tuning your application in your environment because you can take a database and a query and play with all the many ways of getting your data. You can record the results to a log file for later analysis. If you are in a project management position, you may wish to make these logs into part of the project review process.
Figure 3. The various options on the IntroRDO form
The log file (Table 4) records the data in a comma-delimited format that you can easily import into Microsoft Excel or Microsoft Access for analysis. IntroRDO records times in milliseconds.
Table 4. An Example of the Generated Log File (Some Columns Deleted)
Object |
Average Time | Min Time | Max Time | RSet Type |
Options |
Type of Access |
DAO | 204 | 180 | 270 | dbOpenTable | Read/Write | |
DAO | 207 | 180 | 270 | dbOpenSnapShot | Read/Write | |
DAO | 208 | 180 | 260 | dbOpenTable | dbForwardOnly + dbReadOnly |
Read Only |
DAO | 209 | 180 | 260 | dbOpenSnapShot | Read/Write | |
DAO | 215 | 180 | 271 | dbOpenTable | Read/Write | |
DAO | 890 | 761 | 1021 | dbOpenDynaset | Read/Write | |
DAO | 1039 | 791 | 1532 | dbOpenDynaset | Read/Write | |
RDO | 86 | 70 | 170 | rdOpenForwardOnly | Read/Write | |
RDO | 86 | 70 | 151 | rdOpenKeyset | Read/Write | |
RDO | 86 | 70 | 171 | rdOpenStatic | Read/Write | |
RDO | 97 | 80 | 181 | rdOpenDynamic | Read/Write |
If you do not find a significant performance difference between the RDO and the DAO, stick to the DAO and save conversion time. If the RDO performs significantly better, you must balance the performance improvement benefits against the conversion expense.
This article introduced you to the RDO. I believe that the RDO is the way to go for many corporate developers because of the simplicity of a DAO-type object, the performance of direct API calls, and a very small resource footprint. Corporations can save money by cutting development hours and getting better performance from older workstations without the hardware upgrade expense. The RDO is a complex data access method with many possible configurations, so take some time to try different configurations to get the best performance before you start coding.
If you have comments you wish to share with me, drop me an e-mail message at KenL@Microsoft.Com. If you have suggestions for improving the RDO, the DAO, or Visual Basic, e-mail VBWish@Microsoft.Com.
Microsoft licenses the Remote Data Object for use only with the Enterprise Edition of Visual Basic 4.0. You can use it from Microsoft Excel, Microsoft Access, and Microsoft Project if you have the Visual Basic Enterprise Edition installed on the same PC. You cannot redistribute it with solution code (Microsoft Excel, Microsoft Access, and Microsoft Project). It is legal to create an OLE Automation server in Visual Basic 4.0 using RDO and to redistribute this Visual Basic server. You can use this OLE Automation server from your solution code. This introduces you to the concept of three-tier client/server solutions, the direction in which client/server solutions is moving.
Lassesen, Ken. "Building Add-Ins for Visual Basic 4.0."msdn_addinvb4 July 1995. (MSDN Library, Technical Articles)
Lassesen, Ken. "Mapping the Data Access Object: DAO 3.0."msdn_mapdao30 July 1995. (MSDN Library, Technical Articles)
Lassesen, Ken. "Mapping the Remote Data Object: RDO 1.0."msdn_maprdo10 August 1995. (MSDN Library, Technical Articles)
Microsoft SQL Server version 6.0 for Microsoft Windows NT, Programming SQL Distributed Management Objects. 1995. (MSDN Library, Platform, SDK, and DDK Documentation)
Microsoft Visual Basic Programming System for Windows version 4.0, Building Client/Server Applications with Visual Basic. 1995. (Ed. note: You can find an updated version of “Building Client/Server Applications with Visual Basic” in the MSDN Library, Developer Products, Visual Basic 5.0 Documentation.)
Vaughn, Bill. A Hitchhiker's Guide to Visual Basic 4 and SQL Server 6. Redmond, WA: Microsoft Press, February 1996. [This is the planned publication date.]
Vaughn, Bill. A Hitchhiker's Guide to VBSQL: The Developer's Roadmap to the Visual Basic Library for SQL Server. 3d ed. Redmond, WA: Beta V, 1994. Available via Fawcette Technical Publications at (800) 848-5523.