September 25, 1998   |   Back to Microsoft Access Articles and Sample Apps | |
ADO ProgrammingBy Paul Litwin This article originally appeared in the conference proceedings for the Microsoft Office and VBA Solutions Conference and Exposition, held in London, England, July 12-15, 1998, and is reprinted with permission of Informant Communications Group, Inc. Please visit their Web site at http://www.informant.com/mod/index.asp. Download the sample code discussed in this article. ADO is a lean, low-memory-footprint, data-access model (a programmatic language for interacting with databases) that’s optimised for access over the Internet and intranets. If you’re familiar with other Microsoft data models — that is, if you’ve used DAO in Access or RDO in Visual Basic — you’ll feel right at home with ActiveX Data Objects (ADO). Although you may find several differences between the data access models, you likely will find more similarities than differences. Using the SamplesRequirements. All of the examples require ADO to be installed on your machine. You can acquire ADO by installing Active Server Pages, Visual InterDev, or the OLE DB SDK on your machine. In addition, all but a few of the examples require you to have SQL Server 6.5. The Access examples all use data stored in a SQL Server 6.5 database. The ASP examples require a Microsoft Web server with ASP installed. Some of the examples require access to SQL Server. Others use data stored in an Access database. The Excel and Visual Basic examples require SQL Server. Setting up the sample Access database. Most examples used throughout the talk involve a fictitious Internet meal delivery business called Web_Meals.Com. The Microsoft Access 97 version of this database is named Meals.mdb. Setting up the sample SQL Server database. I also created a Microsoft SQL Server 6.5 version of the Meals database. However, before you can use this version of the database, you must run a script that creates the database. Take the following steps to accomplish this:
Running the examples. There are four sets of examples. After you’ve installed the Meals databases, follow these instructions to use the examples. Access Examples
ASP Examples
Excel Example
Visual Basic 5 Example
ODBC, OLE DB and ADOODBC and OLE DB are Windows application programming interfaces (APIs) for accessing data. The older ODBC specification provides data access to primarily relational SQL-based databases, and it does this well. OLE DB, Microsoft's next-generation data-access specification, allows data access to a much broader set of data providers. Those providers include non-relational database systems, e-mail systems, and CAD/CAM data stores, as well as the classic relational database system. OLE DB does not replace ODBC. In fact, OLE DB includes a data provider that allows you to use it with ODBC data sources. The important point to realise, however, is that Microsoft plans for OLE DB to go far beyond ODBC in providing universal access to data, no matter how or where it gets stored on the enterprise. Where does ADO fit into this picture? ADO is a high-level interface to OLE DB. For now, you can conduct business as usual because you can use ADO to access OBDC data sources through OLE DB's ODBC data provider. In the future, however, you will be able to use ADO to access your e-mail system or some other non-relational data provider. Microsoft has publicly stated that ADO eventually will replace the company's current myriad of data-access models, including DAO and RDO, so keep in mind that ADO is not just for Internet/intranet data access. ADO is the Microsoft data access model for the future! The ADO Object ModelThe ADO object model, shown in Figure 1, is much simpler than the DAO and RDO object models. Figure 1: The top-level object in ADO is the Connection object, which contains Command and Recordset objects and the Errors collection. As you can see in Figure 1, ADO, unlike DAO and RDO, lacks a collection of tables, or an environment, workspace, or engine type object. The major ADO objects and collections are:
Accessing Data Using ADOThe following ASP file (emp1.asp) uses ADO to grab a record from the Access 97 WebMeals.mdb database using the WebMeals ODBC system data source. Following is the complete emp1.asp page: |
|
You can see the preceding code in action in Figure 2. |
|
Figure 2: This page uses ADO code to extract a single record from the qryCurrentEmployees query and display it on the Web page. Note: If you want to run this example, you need to create a System data source called WebMeals on your Web server machine that points to the WebMeals.mdb database. Pure HTML. The emp1.asp file seems as if it contains a lot of code, but it’s actually quite simple if you break it down into several smaller parts. The first part of the ASP page is just pure HTML: |
|
Declaring variables. In this stretch of code, I declare two variables. Remember that all variables are variants in VBScript (the default ASP scripting language), so you don’t find As Datatype in the VBScript Dim statement.
Creating the Recordset object. You create the recordset in several steps:
If you’re used to DAO or RDO in VBA, this way of creating objects may seem strange. VBA allows you to early bind your objects — when you write your code, you can set a reference to the object model’s type library. (Sometimes, this reference gets created automatically.) You avoid having to use the CreateObject function. Unfortunately, because the VBScript language doesn’t support early binding, you must create all objects using the CreateObject function. You create ASP objects using the Server object, which provides access to methods and properties of the Web server.
For Access data sources, you can refer to both tables and queries in your SQL statements. The second argument, in this example "WebMeals", tells ADO the name of the data source to use:
Displaying the data. To display the data from the record returned by the recordset, you must use a mixture of HTML and VBScript. The HTML provides the labels for the data; the VBScript provides the values from the recordset using the names of the fields from the Access query:
You can insert a nonbreaking space into your HTML by using the sequence of characters. Clean-up time. The last section of code closes the Recordset object and ends the HTML:
Now that wasn’t so bad, was it? Flattening the Object ModelIn ADO, the object hierarchy has less emphasis. This means that, unlike DAO and RDO, you don’t have to work your way down the object hierarchy just to create an object. I took advantage of this fact in emp1.asp by directly creating the rst recordset without first creating a connection. What’s the advantage to this flatter object model? You have fewer lines of code and less use of memory. I save a bit of memory because I avoid creating a Connection object. Memory use is especially critical if dozens or even hundreds of clients simultaneously use your Web page. In many cases, however, you need to explicitly create a Connection object because it’s more efficient when you perform multiple recordset or command operations. Having the flexibility is nice, though. Recordset options galore. The Open method of the Recordset object allows you to create different kinds of recordsets. The syntax of the Open method follows:
Source. The first argument of the Open method, Source, represents the source of the recordset. In this case, I used a SQL string in emp1.asp, but I also could have used the name of a table, or a server-stored procedure. ActiveConnection. In the second argument, ActiveConnection, you can do one of the following:
CursorType. You use the third argument, CursorType, to specify the type of recordset to create. A summary of the possible values of this argument appear in Figure 3.
Figure 3: Possible cursor type values. The default recordset cursor is a forward-only recordset. Unlike DAO recordsets, you can update all ADO recordsets, even the static and forward-only recordsets. The different types of recordsets vary in how your recordset reflects changes made by other users. Changes to the records made by other users in static and forward-only recordset are invisible to you. That is, when you create a static or forward-only recordset, you are totally unaware of any changes made by other users. If you don’t need to move backward through the recordset, then use the forward-only recordset because it consumes less memory and is faster than the static cursor. Forward-only recordsets don’t support methods such as MovePrevious, MoveFirst, or MoveLast. Also, forward-only recordsets don’t support the use of the RecordCount property. The keyset recordset is very similar in behavior to the DAO dynaset. The number of records in a keyset recordset never changes. You never find out about additions that other users make, nor do records deleted by other users disappear from your recordset (although the deleted records become inaccessible). However, you do get notification when existing records get updated. The dynamic recordset has no DAO counterpart. This type of recordset is totally dynamic. When you have a dynamic recordset open, you get notices of recordset additions, deletions, and updates. While the dynamic recordset is the most functional, be aware that it’s also the most expensive in terms of memory and speed. LockType. The fourth argument specifies the type of locking to employ. A summary of the possible lock types appears in Figure 4.
Figure 4: Possible LockType values. Set LockType to 1, read only, if you aren’t updating records. Generally, if you’re updating records, you should set LockType to 3, optimistic locking. All data providers support this type of locking. When you choose optimistic locking, the record locks only while ADO physically updates the record. In contrast, a Locktype of 2 provides pessimistic locking (if the data provider supports it). In this type of locking, the record locks while you edit the record. Some data providers may not offer pessimistic locking. Setting the LockType argument to 4 tells ADO you want to employ batch updating. This form of updating allows you to download multiple records, update them locally, and submit them back to the data provider in a single batch. Many data providers do not support batch updating. Options. You use the optional Options argument to indicate the type of Source argument you provided. You can set Options to any of the options in Figure 5.
Figure 5: Possible options values. If you submit a SQL string as the Source argument, then set Options to 1. If you set the Source argument to the name of a table, select query or view, then you need to use a value of 2. Not all data providers allow you to use the adCmdTable option. If you want to call a stored procedure or saved action query, then you need to set Options to 4. By default, Options is set to a value of 8 (unknown). In this case, ADO attempts to infer the source type from its contents. Constants — what constants? The constants listed in Figures 3, 4, and 5 are not predefined for you by VBScript as they are when you use ADO from VBA (with a reference to the ADO type library). You can use these constants in two ways in your code:
You can find these include files in the Program Files\Common Files\System\Ado folder on your Web server. (Older versions of the Microsoft Web servers place these files in the \Inetpub\wwwroot\ASPSamp\Samples folder.) To incorporate an include file in your ASP file, use a server-side include statement like the following:
You need to place this preceding statement outside of any script. For example, take a look at the next block of code: |
|
If you need only a few constants, however, using the entire advobs.inc include file is probably overkill and wastes a lot of memory. Another Recordset example. The emp1.asp example displays a single record on the Web page. More typically, you want to display a group of records on a single page using an HTML table — as shown in the second example, cust1.asp. The second example draws its records from the SQL Server version of the Meals database. Following is the complete ASP file: |
|
You can see the preceding block of code in Figure 6 as it appears in Internet Explorer 4.0.
Figure 6: This page uses ADO code to fill an HTML table with records from the tblCustomers table. Note: If you want to run this example, you need to create a System data source called WebMealsSQL on your Web server machine that points to the WebMeals SQL Server database. (Alternately, if you don’t have access to SQL Server you could point this data source to the Access version of the Meals database instead.) In the next few sections, I highlight some of the key pieces of code in cust1.asp. Declaring variables and constants. After the requisite HTML header, the VBScript code in cust1.asp declares its variables and several constants that the Open method uses: |
|
Using constants in your VBScript code instead of arbitrary numbers is always a good idea. This practice makes your code more readable. Opening a connection. The following code from cust1.asp creates a connection object and opens it: |
|
Because the WebMealsSQL data source points to a SQL Server database, you must provide username and password arguments. For Access databases, these arguments are optional. (If you’re using Access instead of SQL Server, you’ll need to change the last line to: |
|
The first argument of the Connection object’s Open method, ConnectionString, may refer to either an ODBC data source name (DSN) or a detailed ADO connection string. All the examples in this paper use the former, but in some cases you may need to provide further information. You need a detailed ADO connection string when you’re using a non-ODBC data provider. Creating the Recordset. This section of code creates the recordset using a SQL statement and links it up to the existing Connection object: |
|
What if there aren’t any records? Cust1.asp includes the following If..Then statement to handle the case where the query returns no records: |
|
The preceding code uses the EOF property of the recordset to check if any records are present. If the query returns an empty recordset, ADO sets the EOF property to True, otherwise it sets EOF to False. Displaying the table. If you have records in the recordset (rst.EOF = True), then you create the table header with this code: |
|
After you create the header, you need to loop through each of the records and plug the FirstName, LastName, and City values into the rows of the HTML table. Do this by using a Do While loop and the MoveNext method of the recordset: |
|
Don’t forget the MoveNext method. (Does this sound like a voice of experience speaking?) If you do, then the Do While loop prints the same record in an infinite loop, producing a Web page of infinite length — probably not what you intended. Updating Data Using ADO, you can update records, add new records, and delete records. The following sections demonstrate how to accomplish these feats with several examples. Adding records to a Recordset. To add a new record to an ADO recordset, you use the AddNew and Update methods of a Recordset object. First, though, you need to create a Recordset object. After you create a non-read-only recordset, you’re ready to add a new record. The code to add a record should look something like this: |
|
Adding a new record is always a three-step process:
The next example consists of two files: an HTML file that captures a new record using an HTML form (addcust1.html); and an ASP file (addcust1.asp) that adds the new record to the tblCustomers table using the AddNew and Update methods. Following is the addcust1.html file: |
|
The addcust1.html page appears in Figure 7. |
|
Figure 7: Adding a new record to the tblCustomers table. And following is the addcust1.asp file that addcust1.html posts its data to: |
|
Because addcust1.asp needs to update data, I created a keyset recordset with optimistic locking enabled: |
|
Values from the calling HTML form get sucked into the code using the Response object, as in: |
|
When you successfully submit the new record, a confirmation page, addcust1.asp, is displayed. What about handling errors? The code in update1.asp assumes that the new record is always successfully added to the tblCustomers table. Unfortunately, the fact that the record was added at this point is not a foregone conclusion. Several things can and will (from time to time) go wrong. For example, the update may fail for the following reasons.
Always remember the one rule in computing: If something can go wrong, it will go wrong — especially when you’re on vacation! So what happens if you encounter an error while saving the record? The ASP scripting engine prints an error to the page, as shown in Figure 8. The message varies, depending on the type of error. No matter what the error, the result isn’t very professional looking. |
|
Figure 8: An error message as displayed by default. VBScript error handling. Unlike its cousin VBA, VBScript has limited custom error-handling capabilities. VBScript doesn’t support the use of procedure-level error handlers. The error-handling capabilities of VBScript consist of the ability to turn off the default error handling (which allows you to intercept the errors and handle them yourself) with: |
|
and the ability to turn default error handling back on with
In addition, when you turn off the default error handling, you can use the VBScript Err object to get information about the error. For example, you can check if an error has occurred with an If..Then statement like the following: |
|
Whenever you check its value, Err.Number equals:
You also have access to the error message that VBScript would have displayed. You can find this message stored in the Description property of the Err object. You can display the message with the following code: Response.Write Err.Description You must test the value of Err.Number immediately because Err.Number resets after each statement and thus contains only information regarding the most recent error. The ADO errors collection. In addition to the VBScript Err object, the ADO Connection object has an Errors collection that may contain multiple entries regarding the last ADO error. The Errors collection’s Count property tells you how many errors, if any, are in the collection. Thus, with code like this you can display all the errors that may have occurred from an ADO operation. (In the following example, con points to an active ADO Connection object): |
|
Which one should I use? Why use the ADO Errors collection when you can use the simpler VBScript Err object? A particular ADO operation sometimes may generate a series of errors. In these cases, the ADO Errors collection provides additional information on the problem. In most cases, however, the VBScript Err object and the ADO Errors collection return the same information, so you’re fine, in most cases, if you use the simpler Err object. No matter which form of error reporting you decide to use, you must use the VBScript On Error Resume Next statement to tell VBScript you want to intercept the errors that VBScript normally handles. An error-handling example. If you decide to implement custom error handling in your ASP pages, be prepared for a bunch of extra code. Because VBScript lacks any way to define a procedure-level error handler, you need to check for the occurrence of an error after every VBScript statement — or at least after every VBScript statement that you think may cause an error. One way to lessen the error-handling load is to create a function to take care of some of the tedium of handling errors. I create such a function in a modification to the addcust1.asp file named addcust2.asp. The complete code for addcust2.asp is on the CD. Here, I highlight just the key parts of the ASP page. The HandleErrors function takes care of the error-handling in the following code: |
|
The main code from addcust2.asp calls HandleErrors immediately after every statement that may cause an error. For example, the code calls HandleErrors after the Update method: |
|
The main code passes HandleErrors the error number and the description. I define a variable, varErrors, to keep track of the number of errors that occurred. This approach works because HandleErrors returns a 0 if no error occurs or a 1 if an error occurs. At the end of the main code, I know whether the record addition succeeded and I can display the status of the record addition to the user. |
|
The same error that occurs in Figure 8 appears in Figure 9 using a custom error-handling routine. |
|
Figure 9: This error message is more professional looking and makes it clearer that the record didn’t get added. Going to the trouble of handling errors has two advantages:
You don’t have to display the VBScript or ADO error message to the user. You can always check to see which error has occurred (using Err.Number) and then display more user-friendly versions of some of the most common errors. Updating a Record in a RecordsetUnlike DAO and RDO, there is no Edit method of a recordset in ADO to indicate when you want to begin editing a record. The code to edit a record should look something like the following: |
|
Editing a record is a three-step process:
In fact, even Step 3 is optional. If you forget to use the Update method to save your changes and move to another record, ADO automatically saves your edits for you. Wow! How then do you abandon your edits? Fortunately, ADO recordsets have a method, CancelUpdate, which you can use to throw away your edits. Using the CancelUpdate method can’t be easier:
I’ve created an example that you can use to update records in the tblCustomer table of the SQL Server Meals database. This example uses three files:
In this section, I discuss the updating portion of the example files. Later in this paper, I discuss the delete functionality of the example files. Update.asp. Much of the code in Update.asp is identical to the code from cust1.asp. You can use the following section of code to display the values in a standard HTML table: |
|
The only way the preceding code differs from the code in cust1.asp is the part that creates the first column of the table. The preceding example uses the <A> tag to create a hyperlink that displays the customer’s name from the Name field, but sets up a hyperlink to UpdRec.asp, passing this page the string "CustomerId=" and the value of the CustomerId for that record. This sends HTML to the browser that looks like the following (for the record with a CustomerId = 7):
Take a look at Figure 10 to see how update.asp appears in Netscape Navigator. |
|
Figure 10: The Update.asp page lets you select any customer for updating. UpdRec.asp. The code behind UpdRec.asp is responsible for displaying the values from the requested record, as well as two submit buttons that determine the action to take on the record. Following is the complete UpdRec.asp file: |
|
The preceding page creates a recordset based on the CustomerId value passed to the page by the update.asp page. Following is the SQL for the recordset: |
|
The following statement sets up the form to post its data to the UpdAct.asp page when the user clicks the Submit button: |
|
The form displays the CustomerId value as regular HTML text rather than as a form field to prevent the user from changing it. I also include, however, a hidden control on the form that contains the CustomerId value so it can be passed along to the UpdAct.asp page: |
|
A good way to pass along information between pages is to store it in hidden controls. The UpdRec.asp page contains two Submit buttons. Because I include Name subtags for both Submit buttons and give them the same name — in this example Submit (but there’s nothing special about this name) — the name and value pair post to UpdAct.asp (see Figure 11). Thus, the code in UpdAct.asp can tell which button the user clicks. This trick doesn’t work unless you include the Name subtag in the Submit button tags. |
|
Figure 11: You can edit a customer record using the UpdRec.asp page. UpdAct.asp. The UpdAct.asp page completes the update operation by opening a recordset based on the edited record from UpdRec.asp and saving the updated values back to the record. Much of this code is similar to the two other pages, so in this section I highlight only key portions of the code. In this portion of code from UpdAct.asp, the LockType argument of the rst.Open method is set to "adLockOptimistic" to allow for editing: |
|
The following If..Then statement determines which of the Submit buttons on UpdRec.asp the user clicked: |
|
The updated values carry over from UpdRec.asp, and the Update method saves the changes to the database in this stretch of code: |
|
Deleting a Record from a RecordsetDeleting a record in ADO is pretty simple. You use the Delete method of the Recordset object to delete the current record. The code looks like the following:
Deleting a record is a two-step process:
Following is the code in UpdAct.asp that deletes the record: |
|
Executing Queries and Stored ProceduresThe ADO Command object makes it easy for you to execute Access action queries or server-stored procedures. You can use the Command object in one of two ways:
To execute a Command object on a stored procedure that doesn’t return records or an Access action query (which never returns records), you use the following syntax:
When you need to create a recordset for a stored procedure, you use this syntax instead:
If you pass a variable to the Execute method’s RecordsAffected argument, the argument returns the number of records affected by the action query or stored procedure. This tactic is a good way to determine if the action query or stored procedure succeeded. You can use the Parameters argument to pass an array of parameters to the Execute method. In most cases, setting the parameter values prior to using the Execute method is better. You can use the Options argument to tell the Execute method the type of command you are executing. The possible values are the same as for the Recordset object’s Open method, which I list in Table 3. You can also use the CommandType property of the Command object to set this value prior to using the Execute method. All of the Execute arguments are optional. A stored procedure example. The UpdAct.asp file creates a recordset to delete a record. In most cases, using an action query or stored procedure to perform this sort of activity is much more efficient. Another example on the CD-ROM shows you how to do this using a stored procedure. The stored procedure example on the CD-ROM uses two files:
Because the updatesp.asp file is almost identical to the update.asp file discussed in a previous section, I don’t present it here (but you can find the file on the CD-ROM). The only real difference in the file is that it calls sp.asp instead of updrec.asp with the following code: |
|
The code in the sp.asp file deletes the requested record by calling the procDeleteCustomer stored procedure in the SQL Server version of the Meals database. This stored procedure accepts a single parameter, the CustomerId of the record to delete. Following is the complete sp.asp file: |
|
Create the Command object with this code from sp.asp:
After you create the Command object, you need to link it to a previously created Connection object with code like the following:
The next two statements identify the stored procedure: |
|
If the stored procedure has any parameters, you can set the values of the stored procedures using the Command object’s Parameters collection. Although, the Parameters collection begins numbering with 0, SQL Server reserves the first parameter, parameter 0, for the return value of the stored procedure, even if you haven’t explicitly defined a return value. Thus, the first input parameter is parameter 1. Following is the code in sp.asp used to pass the CustomerId (from the updatesp.asp page) to the stored procedure.
This next statement executes the stored procedure, sticking the number of records affected by the stored procedure into the varRecords variable:
To determine whether the stored procedure worked, all you need to do is check the value of varRecords like this: |
|
An action query example. Executing an Access action query in ADO is very similar to executing a stored procedure. In fact, the code is virtually identical. The updateaq.asp and aquery.asp files demonstrate how to execute an Access delete query using an ADO Command object. The only significant difference to executing an Access action query is in how the parameters are numbered. Because Access action queries lack any return value, the first input parameter in Access queries is parameter 0, not parameter 1 as with SQL Server stored procedures. Following is the code from aquery.asp that executes an Access delete query named qryDeleteCustomer. The query contains a single parameter. |
|
You’re done! This paper has been adapted, with permission, from Intranet & Web Databases for Dummies by Paul Litwin (IDG Books 1997). Copyright © 1997 IDG Books. Paul Litwin is a senior consultant with MCW Technologies focusing on application development employing Access, Visual Basic, Visual InterDev, Microsoft Office, SQL Server, and related technologies. MCW Technologies wrote the wizards that are part of Visual InterDev. Paul has written numerous articles for publications including Smart Access, Visual Basic Programmer's Journal, and PC World. He has also written several books including the Access 97 Developer's Handbook (and two earlier editions of this book), Intranet & Web Databases for Dummies, VBA for Dummies Quick Reference, and Microsoft Access 95 How-To. Paul is currently at work on Visual InterDev Developer's Handbook. He trains developers for Application Developers Training Company and has spoken at a variety of U.S. and international conferences. You can reach Paul at plitwin@mcwtech.com. |
Write us at MSOffDev@microsoft.com.
|
||
© 1998 Microsoft Corporation. All rights reserved. Legal Notices. Last Updated: June 26, 1998
|
||
Best experienced with Click here to start.
|
||