Microsoft Access 2002
Using ADO in Microsoft
Access 2002
Microsoft® Product Support Services White Paper
Written by Keith Fink
Published on June 21, 2001
Abstract
This paper discusses how to use ActiveX Data Objects (ADO) with forms and reports in Microsoft Access 2002.
It also discusses the new Client Data Manager (CDM) in Microsoft Access 2002 and how to use ADO with it.
The CDM is a new OLE DB service provider created specifically for use in Microsoft Access 2002. It handles
data binding between the database engine (Jet or SQL) and Access objects such as forms, reports, and data
access pages. This gives developers more flexibility and better update semantics when they are writing ADO
code to bind ADO Recordset objects to Access forms.
The information contained in this document represents the current view of Microsoft Corporation on the issues
discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it
should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the
accuracy of any information presented after the date of publication.
This White Paper is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS OR
IMPLIED, AS TO THE INFORMATION IN THIS DOCUMENT.
Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under
copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or
transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for
any purpose, without the express written permission of Microsoft Corporation.
Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights
covering subject matter in this document. Except as expressly provided in any written license agreement from
Microsoft, the furnishing of this document does not give you any license to these patents, trademarks,
copyrights, or other intellectual property.
The example companies, organizations, products, people and events depicted herein are fictitious. No
association with any real company, organization, product, person or event is intended or should be inferred.
 2001 Microsoft Corporation. All rights reserved.
Microsoft, Microsoft SQL Server, and Windows are either registered trademarks or trademarks of Microsoft
Corporation in the United States and/or other countries.
The names of actual companies and products mentioned herein may be the trademarks of their respective
owners.
CONTENTS
INTRODUCTION ............................................................................................................. 1
MICROSOFT CLIENT DATA MANAGER (CDM) .............................................................. 2
Client Data Manager Features
2
Improved Update Semantics with Multi-Table Views
2
Improved Autolookup
2
Provides Stable Cursors
3
Visible Defaults
4
Using ADO Connections with CDM
4
Opening ADO Connections with CDM
4
Sharing an Access ADO Connection Using CDM
6
BINDING MICROSOFT ACCESS FORMS TO ADO RECORDSETS .................................. 8
Microsoft SQL Server
8
Microsoft Jet
9
ODBC
10
Oracle
11
Batch Updates Unsupported
13
USING RECORDSETS WITH COMBO BOX AND LIST BOX CONTROLS ...................... 14
Setting the Control Recordset Property
14
Retrieving a Recordset from the Control
16
USING ADO RECORDSETS WITH MICROSOFT ACCESS REPORTS ........................... 18
Setting the Report Recordset Property
18
Assigning the Recordset Property of an Ungrouped Report
20
Assigning the Recordset Property of Grouped Reports
20
Retrieving the Report Recordset Property
22
FOR MORE INFORMATION .......................................................................................... 24
INTRODUCTION
With the release of Microsoft Access 2000, developers had the choice of building
Microsoft Access solutions that used the Jet database engine or that used
Microsoft SQL Server™ or the Microsoft Data Engine (MSDE). Historically, the Jet
database engine was the default database engine for use in Microsoft Access, and
it offered rich data update semantics to application developers.
To make developing client/server applications easier, Microsoft Access 2000
introduced project (.adp) files. In Access 2000, a Microsoft Access project (.adp)
file provided access to a Microsoft SQL Server database through the OLE DB
component architecture. This allowed developers to create SQL Server databases
and an application interface within Microsoft Access without having to load the Jet
database engine.
Even though project files allowed developers to create client/server applications
in Microsoft Access 2000, there were some definite limitations. First, .adp files did
not offer the rich update semantics with SQL Server that developers were used to
with the Jet engine. Second, developers could write DAO code to further enhance
their Access/Jet solutions, such as binding a form to a DAO recordset. However,
client/server applications in Microsoft Access 2000 did not offer the same feature
set with ADO.
Microsoft Access 2000 used the Microsoft Data Shaping OLE DB Provider
(MSDataShape) and the SQL Server OLE DB Provider to provide data access to
SQL Server databases from within Microsoft Access project (.adp) files. While
these providers enabled developers to work with SQL Server objects directly from
within Microsoft Access, there were still a number of differences in developing
applications for the Jet database engine and Microsoft SQL Server/MSDE.
To help bridge the gap between developing Access applications for Jet databases
and SQL Server databases, the Microsoft Access team created the Client Data
Manager (CDM).
Product Support Services White Paper
1
MICROSOFT CLIENT DATA MANAGER (CDM)
The CDM is an OLE DB service provider whose goal is to make data access
between Microsoft Access and SQL Server very similar to data access between
Microsoft Access and the Jet database engine.
The CDM is a cursor layer that sits between the data consumer and the
Windows® Cursor Engine to enable improved update semantics with the existing
MDAC components. There are a number of features the CDM offers in Microsoft
Access 2002 to make data access with SQL Server very similar to data access
with Microsoft Jet.
Client Data Manager Features
Improved Update Semantics with Multi-Table Views
Improved update semantics is one of the best improvements the CDM offers
in Microsoft Access 2002. In Microsoft Access 2000, forms that were based on
multi-table views were read-only unless the developer supplied a table name
for the form's UniqueTable property. This would allow the form to be
partially updateable, so that only fields from the table specified in the
UniqueTable property could be edited.
When using Microsoft Access 2002 with Microsoft SQL Server 2000, the CDM
automatically retrieves unique table information by calling SQL Server stored
procedures. This allows fields from both sides of a multi-table view to be
updateable without having to supply the form's UniqueTable property. This
allows developers to create client/server solutions that are more similar to
Access solutions based on Jet.
Improved Autolookup
Another significant improvement the CDM offers client/server applications is
improved Autolookup. Autolookup is a feature that refreshes fields from the
Product Support Services White Paper
2
primary table of a one-to-many relationship in a multi-table query when the
user edits the foreign key field. For example, if you modified the value in the
foreign key field between a Customers table and an Orders table (typically, a
customer identifier such as CustomerID), the fields from the Customers table
should be refreshed automatically to display the values that correspond with
the customer that was selected in the foreign key field.
In Microsoft Access 2000, developers had to write custom resynchronization
commands using the form's ResyncCommand property. Even though this
enabled the Autolookup feature to work, the fields from the primary table
were updated only when the record was saved, not when the foreign key field
was modified.
For more information, please see the following article in the Microsoft
Knowledge Base.
ACC2000: How to Simulate AutoLookup with a Stored Procedure in Access
Client/Server
http://support.microsoft.com/support/misc/kblookup.asp?id=Q239886
When using Microsoft Access 2002 with Microsoft SQL Server 2000,
developers do not need to write custom resynchronization commands.
Microsoft Access retrieves these automatically by calling SQL Server stored
procedures. Microsoft Access 2002 also shows the impact of foreign key
updates immediately, provided that the recordset contains the primary and
foreign keys of the two tables.
Provides Stable Cursors
One of the most frustrating limitations in Microsoft Access 2000 project (.adp)
files was unstable cursors. An unstable cursor is a set of records whose sort
order is lost whenever users edited a record.
For example, assume a user sorted a form by using the Sort Ascending
command on the Access toolbar, and then decided to add a new record. After
committing the record, Microsoft Access would lose the sort order that the
user had just applied.
For more information, please see the following article in the Microsoft
Knowledge Base.
ACC2000: Sort Order Lost When You Edit Records in a Microsoft Access
Project
http://support.microsoft.com/support/misc/kblookup.asp?id=Q257532
The CDM solves this problem in Microsoft Access 2002 by providing stable
cursors when users modify records after sorting or filtering the recordset.
Product Support Services White Paper
3
Visible Defaults
In Microsoft Access 2000, users were unable to see the default values of a
field when editing data in a form. In order to get this functionality, the
developer had to set the form control's DefaultValue property to the desired
value. However, if the default value for the field changed on the server, the
developer had to change the form as well.
The CDM solves this limitation in Access 2002 by automatically showing
default values on new records in forms.
Using ADO Connections with CDM
Because the CDM is an OLE DB service provider, you can use it use it with ADO in
a Microsoft Access 2002 application.
Note The CDM (Microsoft Access 10.0 OLE DB provider) was designed to be used
in Microsoft Access. It is unsupported for use in other applications.
There are two ways that you can use ADO connections created with the CDM. You
can open and manage your own ADO connection using this provider, or you can
share the same ADO connection that Microsoft Access is using to the database.
Opening ADO Connections with CDM
Opening an ADO connection using the CDM is very similar to opening and
managing ADO connections with other OLE DB providers. Because the CDM is
an OLE DB service provider, it is not designed to access a specific database
format such as Jet or SQL Server. Rather, it is designed to provide services to
data retrieved by a separate OLE DB provider, such as the Jet or SQL Server
OLE DB providers. Therefore, your ADO code must supply an OLE DB data
provider when using the CDM.
To specify the CDM as the provider for your ADO connection, set the
connection's Provider property to the string "Microsoft.Access.OLEDB.10.0".
To specify the data provider for your ADO connection, set the connection's
Data Provider property to the OLE DB provider specific string.
Sample Code for Opening CDM Connections to Microsoft SQL Server
This sample code opens and closes an ADO connection using the CDM and the
Microsoft SQL Server OLEDB providers:
Sub OpenSQLConnectionWithCDM()
Dim cn As ADODB.Connection
Set cn = New ADODB.Connection
'Use the Microsoft Access 10.0 (CDM) and
'SQL Server OLEDB providers to open the connection
With cn
.Provider = "Microsoft.Access.OLEDB.10.0"
.Properties("Data Provider").Value = "SQLOLEDB"
'You will need to replace the following properties
Product Support Services White Paper
4
'with the name of your SQL Server, user ID, password,
'and database to open the connection.
.Properties("Data Source").Value = "MySQLServer"
.Properties("User ID").Value = "sa"
.Properties("Password").Value = "mypassword"
.Properties("Initial Catalog").Value = "NorthwindCS"
.Open
.Close
End With
Set cn = Nothing
End Sub
If you want to specify all of this information in one connect string, you can
use code similar to the following:
Sub OpenSQLConnectionWithCDM()
Dim cn As ADODB.Connection
Dim strADOCon As String
strADOCon = "Provider=Microsoft.Access.OLEDB.10.0;" & _
"Data Provider=SQLOLEDB;Data Source=MySQLServer;" & _
"User ID=sa;Password=mypassword;" & _
"Initial Catalog=NorthwindCS"
Set cn = New ADODB.Connection
cn.Open strADOCon
cn.Close
Set cn = Nothing
End Sub
Sample Code for Opening CDM Connections with Microsoft Jet
This sample code opens and closes an ADO connection using the CDM and the
Microsoft Jet 4.0 OLEDB providers:
Sub OpenJetConnectionWithCDM()
Dim cn As ADODB.Connection
Set cn = New ADODB.Connection
With cn
.Provider = "Microsoft.Access.OLEDB.10.0"
.Properties("Data Provider").Value = "Microsoft.Jet.OLEDB.4.0"
.Properties("Data Source").Value = "C:\Program Files\" & _
"Microsoft Office\Office10\Samples\Northwind.mdb"
.Open
.Close
End With
Set cn = Nothing
End Sub
If you want to specify all of this information in one connect string, you can
use code similar to the following:
Sub OpenJetConnectionWithCDM()
Dim cn As ADODB.Connection
Dim strADOCon As String
strADOCon = "Provider=Microsoft.Access.OLEDB.10.0;" & _
"Data Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\Program Files\Microsoft Office\" & _
"Office10\Samples\Northwind.mdb"
Set cn = New ADODB.Connection
cn.Open strADOCon
cn.Close
Product Support Services White Paper
5
Set cn = Nothing
End Sub
Sharing an Access ADO Connection Using CDM
Because Microsoft Access opens and manages an ADO connection to the
currently open database (Jet or SQL Server), it is important that the
connection be accessible for developers writing ADO code within their
application. This keeps developers from having to open and manage their own
ADO connections to the database when Microsoft Access already has an open
connection. To do this, you can use the Connection or AccessConnection
properties exposed by the Microsoft Access object model.
Connection
The Connection property was introduced in Microsoft Access 2000 and is
accessible through the CurrentProject object within the Microsoft Access
object model. Similar to the way the CurrentDb property exposes a DAO
Database object for the currently open database in Microsoft Access, the
Connection property exposes an ADO Connection object for the currently
open database (Jet or SQL Server).
AccessConnection
The AccessConnection property is a new property in Microsoft Access 2002
and is also accessible through the CurrentProject object within the Microsoft
Access object model. Like the Connection property, it allows developers to
share the ADO connection that Microsoft Access is using for the currently
open database.
The difference between these properties depends on whether you are using a
Microsoft Access project (.adp) file connected to a Microsoft SQL Server
database or a Jet database (.mdb) file in Microsoft Access.
When you are using an Access project in Microsoft Access 2002, the
Connection and AccessConnection properties are functionally identical.
Both properties return an ADO connection using the Microsoft Access 10.0
OLE DB provider (Microsoft.Access.OLEDB.10.0) and the SQL Server OLE DB
data provider (SQLOLEDB). Here is a typical connection string that is returned
by either property for a project file connected to a SQL Server database:
Provider=Microsoft.Access.OLEDB.10.0;Persist Security Info=True;Data
Source=MySQLServer;User ID=sa;Password="mypassword";Initial
Catalog=NorthwindCS;Data Provider=SQLOLEDB.1
Therefore, if you are writing ADO code in a Microsoft Access 2002 project file,
you can use either property interchangeably because each returns the same
connection. For example, in a Microsoft Access 2002 project file, the following
two code snippets are functionally equivalent:
Dim cn As ADODB.Connection
Set cn = CurrentProject.Connection
Product Support Services White Paper
6
Dim cn As ADODB.Connection
Set cn = CurrentProject.AccessConnection
When using a Jet database in Microsoft Access 2002, the Connection and
AccessConnection properties return different ADO connections. You can use
either property when you are writing ADO code in a Jet database. However,
there are certain situations where each property is useful. The following is a
breakdown of what each property returns in Microsoft Access 2002, and what
you should consider before using these properties.
The Connection property behaves exactly as it did in Microsoft Access 2000.
It returns an ADO connection that uses the Microsoft Jet OLE DB provider
(Microsoft.Jet.OLEDB.4.0 provider). For example, here is a typical connection
string that is returned for a Jet database that is using the Connection
property:
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Program
Files\Microsoft Office\Office10\Samples\Northwind.mdb
There are several scenarios where you should use the Connection property
when using Jet databases. If your application requires any of the following
features, you should continue to use the Connection property in your code:

Jet and Replication Objects (JRO) code.

Microsoft ADO Extensibility for DDL and Security (ADOX) code.

ADO code that requires Index support for ADO recordsets, such as the
Seek method.
The CDM does not fully support the required OLE DB interfaces necessary for
these features to work. If your application requires these features, or if you
want to ensure complete backwards compatibility with your Access 2000 ADO
code, you should use a connection created with the Microsoft Jet OLE DB
provider, which is returned by the Connection property.
The AccessConnection property returns an ADO connection that uses the
Microsoft Jet OLE DB data provider (Microsoft.Jet.OLEDB.4.0) and the
Microsoft Access 10 OLE DB service provider (Microsoft.Access.OLEDB.10.0).
For example, here is a typical connection string that is returned for a Jet
database that is using the AccessConnection property:
Provider=Microsoft.Access.OLEDB.10.0;Data Source=C:\Program
Files\Microsoft Office\Office10\Samples\Northwind.mdb;User
ID=Admin;Data Provider=Microsoft.Jet.OLEDB.4.0
If you intend to bind Microsoft Access forms to ADO recordsets based on SQL
Server or Jet data, you should use the AccessConnection property, or you
should open and manage an ADO connection that uses the CDM.
Product Support Services White Paper
7
BINDING MICROSOFT ACCESS FORMS TO ADO RECORDSETS
This section describes what is required to create an updateable form that is
bound to an ActiveX Data Objects (ADO) Recordset object.
To bind a Microsoft Access form to a recordset, you must set the form's
Recordset property to an open ADO Recordset object. A form must meet two
general requirements for the form to be updateable when it is bound to an ADO
recordset. The general requirements are:


The underlying ADO recordset must be updateable via ADO.
The recordset must contain one or more fields that are uniquely indexed,
such as a table's primary key.
The requirements for updateability in Microsoft Access forms vary for different
providers. This section describes the additional requirements for updateability
when binding the form to data sources using the Microsoft SQL Server, Jet,
ODBC, and Oracle OLE DB providers.
Microsoft SQL Server
There are two main requirements for supporting updateability when you bind a
form to an ADO recordset that is using Microsoft SQL Server data:

The ADO recordset's connection must use the Microsoft Access 10.0 OLE
DB service provider.

The ADO recordset's connection must use the Microsoft SQL Server OLE
DB data provider.
The following example demonstrates how to bind a form to an ADO recordset that
is based on SQL Server data that shares an ADO connection with Microsoft
Access:
1.
Open the sample project NorthwindCS.adp.
2.
Open the Customers form in Design view.
3.
Clear the RecordSource property so that the form is unbound.
4.
Add the following code to the form's Open event procedure:
Private Sub Form_Open(Cancel As Integer)
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
'Use the ADO connection that Access uses
Set cn = CurrentProject.AccessConnection
'Create an instance of the ADO Recordset class,
'and set its properties
Set rs = New ADODB.Recordset
With rs
Set .ActiveConnection = cn
.Source = "SELECT * FROM Customers"
Product Support Services White Paper
8
.LockType = adLockOptimistic
.CursorType = adOpenKeyset
.Open
End With
'Set the form's Recordset property to the ADO recordset
Set Me.Recordset = rs
Set rs = Nothing
Set cn = Nothing
End Sub
5. Save the form, and then close it.
6. Open the Customers form in Form view.
7. Add, edit, or delete a record in the form.
Note that the form is bound to an updateable recordset that is based on SQL
Server data. If you want to open and manage your own ADO connection with SQL
Server, see the "Sample Code for Opening CDM Connections to Microsoft SQL
Server" section earlier in this white paper.
Microsoft Jet
Even though it is possible to bind a form to an ADO recordset that is using data
from a Jet database, Microsoft recommends you use DAO instead. DAO is highly
optimized for Jet and typically performs faster than ADO when used with a Jet
database. When you bind a form to an ADO recordset using Microsoft Jet data,
you have two alternatives:

The recordset's ActiveConnection property must use the Microsoft
Access 10.0 OLE DB service provider, as well as the Microsoft Jet 4.0 OLE
DB Data provider. The recordset must also be a server-side cursor.
-or-

The recordset's ActiveConnection property must use only the Microsoft
Jet 4.0 OLE DB Data provider and the recordset must be a client-side
cursor.
The following example demonstrates how to bind a form to an ADO recordset in a
Jet database by sharing the ADO connection that Microsoft Access is currently
using:
1. Open the sample database Northwind.mdb.
2. Open the Customers form in Design view.
3. Clear the RecordSource property so that the form is unbound.
4. Add the following code to the form's Open event procedure:
Private Sub Form_Open(Cancel As Integer)
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Set cn = CurrentProject.AccessConnection
Product Support Services White Paper
9
'Create an instance of the ADO Recordset class, and
'set its properties
Set rs = New ADODB.Recordset
With rs
Set .ActiveConnection = cn
.Source = "SELECT * FROM Customers"
.LockType = adLockOptimistic
.CursorType = adOpenKeyset
.CursorLocation = adUseServer
.Open
End With
'Set the form's Recordset property to the ADO recordset
Set Me.Recordset = rs
Set rs = Nothing
Set cn = Nothing
End Sub
5. Save the form, and then close it.
6. Open the Customers form in Form view.
7. Add, edit, or delete a record in the form.
Note that the form is bound to an updateable recordset that is using Jet data.
ODBC
When you bind a form to an ADO recordset that is using data from an ODBC
database, there are two main requirements:


The ADO connection that is used by the recordset must use the Microsoft
OLE DB provider for ODBC.
The ADO recordset must be a client-side cursor.
The following example demonstrates how to open an ADO connection to an ODBC
database and to bind a form to it.
NOTE These steps assume that the ODBC database contains a table named
CUSTOMERS that is identical in structure to the Customers table in the sample
database Northwind.mdb. It also assumes you have created an ODBC data
source named MyDSN that uses the ODBC driver you need to connect to the
back-end database.
1. Open the sample database Northwind.mdb.
2. Open the Customers form in Design view.
3. Clear the RecordSource property so that the form is unbound.
4. Add the following code to the form's Open event procedure:
Private Sub Form_Open(Cancel As Integer)
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim strConnection As String
strConnection = "ODBC;DSN=MyDSN;UID=sa;PWD=;DATABASE=Northwind"
'Create a new ADO Connection object
Set cn = New ADODB.Connection
Product Support Services White Paper
10
With cn
.Provider = "MSDASQL"
.Properties("Data Source").Value = strConnection
.Open
End With
'Create an instance of the ADO Recordset class, and
'set its properties
Set rs = New ADODB.Recordset
With rs
Set .ActiveConnection = cn
.Source = "SELECT * FROM Customers"
.LockType = adLockOptimistic
.CursorType = adOpenKeyset
.CursorLocation = adUseClient
.Open
End With
'Set the form's Recordset property to the ADO recordset
Set Me.Recordset = rs
Set rs = Nothing
Set cn = Nothing
End Sub
5. Because you are not using CurrentProject.AccessConnection, add the
following code to the UnLoad event of the form:
Private Sub Form_Unload(Cancel As Integer)
'Close the ADO connection we opened
Dim cn As ADODB.Connection
Set cn = Me.Recordset.ActiveConnection
cn.Close
Set cn = Nothing
End Sub
6. Save the form, and then close it.
7. Open the Customers form in Form view.
8. Add, edit, or delete a record in the form.
Note that the form is bound to an updateable recordset that is based on ODBC
data.
Oracle
When you bind a form to an ADO recordset that is using data from an Oracle
database, there are two main requirements:


The ADO connection that is used by the recordset must use the Microsoft
OLE DB provider for Oracle.
The ADO recordset must be a client-side cursor.
The following example demonstrates how to open an ADO connection to an
Oracle database and to bind a form to it.
NOTE These steps assume that the Oracle database contains a table named
CUSTOMERS that is identical in structure to the Customers table in the sample
database Northwind.mdb.
Product Support Services White Paper
11
1. Open the sample database Northwind.mdb.
2. Open the Customers form in Design view.
3. Clear the RecordSource property so that the form is unbound.
4. Add the following code to the form's Open event procedure:
Private Sub Form_Open(Cancel As Integer)
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
'Create a new ADO Connection object
Set cn = New ADODB.Connection
With cn
.Provider = "MSDAORA"
.Properties("Data Source").Value = "MyOracleServer"
.Properties("User ID").Value = "username"
.Properties("Password").Value = "password"
.Open
End With
'Create an instance of the ADO Recordset class, and
'set its properties
Set rs = New ADODB.Recordset
With rs
Set .ActiveConnection = cn
.Source = "SELECT * FROM Customers"
.LockType = adLockOptimistic
.CursorType = adOpenKeyset
.CursorLocation = adUseClient
.Open
End With
'Set the form's Recordset property to the ADO recordset
Set Me.Recordset = rs
Set rs = Nothing
Set cn = Nothing
End Sub
5. Because you are not using CurrentProject.AccessConnection, add the
following code to the UnLoad event of the form
6. :
Private Sub Form_Unload(Cancel As Integer)
'Close the ADO connection we opened
Dim cn As ADODB.Connection
Set cn = Me.Recordset.ActiveConnection
cn.Close
Set cn = Nothing
End Sub
7. Save the form, and then close it.
8. Open the Customers form in Form view.
9. Add, edit, or delete a record in the form.
Note that the form is bound to an updateable recordset that is based on Oracle
data.
Product Support Services White Paper
12
Batch Updates Unsupported
A powerful feature of ADO is that it allows developers to create client/server
solutions that can implement batch updates. It does this by allowing developers
to create local, disconnected recordsets for their applications. After the user has
edited the data in the local cursor and is ready to commit the changes to the
server, the application can use ADO to reconnect the recordset to the server, and
then call the UpdateBatch method to commit the changes.
Unfortunately, a major limitation of the form Recordset property is that it does
not support the UpdateBatch method. Although it is possible to bind a form to a
disconnected recordset and edit the data in a local cursor, the UpdateBatch
method will not batch updates to the back-end server.
When editing data in a form, any changes the user makes are stored successfully
in the local cursor. However, Microsoft Access does not notify the Windows
Cursor Engine that the affected records require an update. Therefore when you
call the UpdateBatch method, ADO does not realize that rows have been
modified and does not batch update the changes to the server.
Microsoft Access does not have a particularly elegant solution to this problem.
One approach you can use to obtain this functionality is to unbind the form
completely, and then use ADO code to populate unbound controls on the form
with data from the recordset. This gives you the most control over your
application. However, it requires the most work because you have to manage the
navigation and updating of your recordset completely.
Product Support Services White Paper
13
USING RECORDSETS WITH COMBO BOX AND LIST BOX CONTROLS
In Microsoft Access 2002, it is possible to use ADO and DAO recordset objects
with combo box and list box controls. These controls expose a read/write
Recordset property which developers can use to either set or retrieve a
recordset. If you set the Recordset property, your code will override the
control's RowSource property and Microsoft Access will derive the control's list
portion from records within the recordset. The combo box and list box control
properties function the same way as for natively filled boxes.
Bound Column The column number which contains the bound value
Column Count The number of columns to show in the list
Column Widths The width of each column in the list
Column Heads If set to Yes shows the column name in the list header
Important When setting or retrieving the Recordset property of a combo box
or list box control, the control's RowSourceType property must be set to
Table/View/Stored Proc (or Table/Query in an MDB). If you attempt to set
or retrieve the Recordset property of a combo box or list box control whose
RowSourceType property is not set to Table/View/Stored Proc (or
Table/Query in an MDB), you will receive the following error message:
Setting the Control Recordset Property
When setting the Recordset property of a combo box or list box control, it is
possible to fill the list portion of the control from the recordset. The easiest
approach to fill the list portion of a combo box or list box is to set the
RowSource property to an SQL statement or to the name of a database object
that returns records, such as a table, a query, a view, a stored procedure, or a
function.
It is also possible to define the list by opening an ADO or DAO recordset object,
and then setting the Recordset property of the control to that recordset. This
approach would be useful if you already had an open recordset that provided the
appropriate records for the list, and you did not want Microsoft Access to open an
Product Support Services White Paper
14
additional recordset to fill the list by natively binding it with the RowSource
property.
The following example demonstrates how to fill the list portion of a combo box
control by opening an ADO recordset and setting the Recordset property of the
control.
NOTE You should always use a client-side cursor or make sure the ADO
connection used by the recordset uses the Microsoft Access 10.0 OLE DB service
provider. If you use a server-side cursor, the list portion of the control may not
be filled correctly or the control's current value may not be displayed.
1. Open the sample database Northwind.mdb.
2. Open the Products form in Design view.
3. Clear the RowSource property of the CategoryID combo box control.
4. On the View menu, click Code to view the form's module.
5. Add the following code to the form's Load event procedure:
Private Sub Form_Load()
'Requires reference to Microsoft ActiveX Data Objects
'2.1 Library or higher
Dim rsCategories As ADODB.Recordset
Set rsCategories = New ADODB.Recordset
With rsCategories
.ActiveConnection = CurrentProject.AccessConnection
.Source = "SELECT CategoryID, CategoryName FROM Categories"
.LockType = adLockReadOnly
.CursorType = adOpenKeyset
.CursorLocation = adUseClient
.Open
End With
'Set the Recordset property of the control to the
'ADO recordset we opened, and then close the recordset
Set Me.CategoryID.Recordset = rsCategories
rsCategories.Close
Set rsCategories = Nothing
End Sub
The recordset cursor position has no effect on the list portions of combo box and
list box controls. If you use ADO to move or change position within the recordset,
neither the control's value nor the list portion will change. If you make data
changes to the recordset (for example, adding records, editing records, or
deleting records), the changes may not show up immediately depending on the
provider you are using. If you make data changes to the recordset, you should
call the recordset's Requery method, and then reset the Recordset property of
the control to ensure that the list is showing the correct values.
Product Support Services White Paper
15
Note There is a confirmed problem in Microsoft Access 2002 that causes the
Recordset property to reset when switching between form views. To avoid this
problem, don't allow users to switch views, or fill the control by using the
AddItem method or RowSource property. For more information, please see the
following article in the Microsoft Knowledge Base:
ACC2002: Combo Box or List Box Recordset Is Lost When You Switch Form Views
http://support.microsoft.com/support/misc/kblookup.asp?id=Q282358
Retrieving a Recordset from the Control
You can only retrieve a recordset object from the control if you have already set
its Recordset property. Combo box and list box controls that are filled from the
RowSource property do not return any type of recordset object. If you try to
refer to the Recordset property of a control whose Recordset property has not
been previously set, you will receive the following error message:
To retrieve a recordset from the control whose Recordset property has been
previously set, declare an object variable as the appropriate type
(ADODB.Recordset or DAO.Recordset). Then set the object variable to the
Recordset property of the control.
The following example demonstrates how to retrieve an ADO recordset from the
Recordset property of a combo box in the NorthwindCS sample project file.
1. Open the sample project NorthwindCS.adp connected to the NorthwindCS
database on Microsoft SQL Server 2000.
2. Open the Products form in Design view.
3. Clear the RowSource property of the CategoryID control.
4. Add a command button to the form, and then set its Name property to
cmdRecordset.
5. Add the following code to the form's Open event procedure:
Private Sub Form_Open(Cancel As Integer)
'Requires reference to Microsoft ActiveX Data
'Objects 2.1 Library or higher
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
With rs
Product Support Services White Paper
16
.ActiveConnection = CurrentProject.AccessConnection
.Source = "SELECT CategoryID, CategoryName FROM Categories"
.CursorType = adOpenKeyset
.LockType = adLockOptimistic
.Open
End With
Set Me.CategoryID.Recordset = rs
End Sub
6. Add the following code to the command button's Click event:
Private Sub cmdRecordset_Click()
'Requires reference to Microsoft ActiveX Data
'Objects 2.1 Library or higher
Dim rs As ADODB.Recordset
Set rs = Me.CategoryID.Recordset
Do Until rs.EOF
Debug.Print rs.Fields("CategoryName").Value
rs.MoveNext
Loop
End Sub
7. Open the form in Form view.
8. Click the command button.
9. Press CTRL+G to view the Immediate windows in the Visual Basic Editor.
Note that the Category Names have been printed in the Immediate windows.
Product Support Services White Paper
17
USING ADO RECORDSETS WITH MICROSOFT ACCESS REPORTS
In Microsoft Access 2002, it is now possible to use ADO recordsets with reports in
Microsoft Access project files. Unfortunately, the use of the report Recordset
property is limited to project files. If you try to set or retrieve a report's
Recordset property in a Jet database (.mdb) file, you receive the following error
message:
Setting the Report Recordset Property
Setting the Recordset property of a report is similar to setting the
Recordset property of a form; you simply assign the Recordset property of the
report to a valid ADO recordset that you have already opened. There are two
requirements when setting the report's Recordset property. The first
requirement is that you may set the Recordset property only during the report's
Open event. The second requirement is that the recordset's shape must match
the same shape as the report's design.
Must Set the Recordset Property During the Open Event
The first requirement of setting a report's Recordset property is that you may
set the Recordset property only during the report's Open event. If you try to set
the report's Recordset property from anywhere other than the Open event, you
receive the following error message:
Product Support Services White Paper
18
This applies to subreports as well. You may only set the Recordset property of
subreports from the Open event of the subreport.
NOTE Even though it is possible to set the Recordset property of a subreport,
Microsoft does not recommend that you do so. Microsoft Access ignores the
LinkChildFields and LinkMasterFields properties when setting the Recordset
property. For natively bound subreports, Microsoft Access uses these properties
along with others (RecordSource, Filter, and so on) to build the recordset for
the subreport. Because you are supplying the subreport's recordset by setting its
Recordset property, Microsoft Access ignores any properties that it would
normally use to build the subreport's recordset. Because you can only set the
Recordset property during the subreport's Open event, it is not possible to
create linked subreports using the Recordset property.
Recordset Shape Must Match Report Design Shape
The second requirement of setting a report's Recordset property is that the
recordset shape must match the report's shape in Design view. In other words, if
the report contains one or more grouping levels or aggregate functions (or both),
the ADO recordset must be a hierarchical recordset that contains the same
grouping levels or aggregate functions (or both).
If you try to set the report's Recordset property to an ADO recordset that does
not match the grouped shape of the report, the report may be blank or Microsoft
Access may return the following error message:
Cursor Movement and Data Changes
There are some additional things to consider when setting a report's Recordset
property.
First, cursor movement and position within the ADO recordset does not affect the
report. If you use ADO to move or change position within the recordset, the
report's current page/printing location will not change.
Second, any data changes made to the recordset (for example, adding or editing
records) will be shown on the report only if the record being modified has not
been printed yet. In other words, if you use ADO to modify a record after the
report has formatted and printed it, the changes will not be reflected on the
report, even though the underlying table will be updated.
Product Support Services White Paper
19
Assigning the Recordset Property of an Ungrouped Report
When you use ungrouped reports, setting the Recordset property is very similar
to setting it in forms. During the report's Open event, set the Recordset
property to a recordset that has been opened. The following example
demonstrates how to assign a recordset object to a simple, ungrouped report:
1. Open the sample project NorthwindCS.adp.
2. Open the Customer Labels report in Design view.
3. Clear the RecordSource property so that the report is unbound.
4. Add the following code to the report's Open event procedure:
Private Sub Report_Open(Cancel As Integer)
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
'Use the ADO connection that Access uses
Set cn = CurrentProject.AccessConnection
'Create an instance of the ADO Recordset class and open it
Set rs = New ADODB.Recordset
rs.Open "SELECT * FROM Customers WHERE CustomerID LIKE 'a%'", cn
'Set the report's Recordset property to the ADO recordset
Set Me.Recordset = rs
Set rs = Nothing
Set cn = Nothing
End Sub
5. Press ALT+F11 to return to Microsoft Access.
6. Save the report, and then close it.
7. Print preview the Customer Labels report.
Note that the report is bound to a recordset that only displays records where the
CustomerID field begins with 'A'.
Assigning the Recordset Property of Grouped Reports
When you bind a grouped report to an ADO recordset, the recordset's shape must
match the report's grouping level shape. In order to do this, you must create a
hierarchical recordset using the SHAPE, APPEND, RELATE, and COMPUTE clauses
in your SQL string. It is beyond the scope of this white paper to go into detail on
data shaping syntax; however, this white paper will demonstrate an approach to
determining the correct syntax for a grouped report.
To build the correct SQL grammar for a grouped report, you must determine
what fields are necessary for each group level and the detail section of the
report, plus any aggregate functions the report will need.
The easiest approach to understanding the data shaping syntax required by
grouped reports is to inspect the Shape property of a grouped report. The
Shape property returns a string that represents the ADO shape command that
Microsoft Access is using for the report.
Product Support Services White Paper
20
Using the Report's Shape Property to Understand Shaping Syntax
In Microsoft Access 2002, the Shape property returns the ADO shape command
corresponding to the sorting and grouping of the report. It is possible to use this
property for existing reports to determine how to build the appropriate ADO
shape command for your reports. The following example demonstrates how to
analyze the Shape property for an existing report and determine how it is
applied to the report:
1. Open the sample project NorthwindCS.adp.
2. Open the Products by Category report in Design view. Note that this report
contains one group level, composed of a group header and footer and a detail
section.
3. Note that the group level is grouping records based on the CategoryName
field.
4. Note the fields in the detail section: ProductName and UnitsInStock.
5. Note that the group footer contains a control that uses the Count aggregate
function to count the records in each group.
6. Print preview the report.
7. With the report open in print preview, type the following expression in the
Immediate windows, and then press ENTER:
?Reports("Products By Category").Shape
8. This should return the following ADO shape command:
SHAPE (SHAPE {SELECT CategoryName, ProductName, UnitsInStock FROM
[Products by Category]} AS rsLevel0 COMPUTE rsLevel0,
Count(rsLevel0.[ProductName]) AS __Agg0 BY CategoryName AS __COLRef0)
AS RS_230
9. Concentrate on just the SELECT portion of the SQL statement:
...SELECT CategoryName, ProductName, UnitsInStock FROM [Products by
Category]...
Note that this portion of the statement only selects the fields required by the
detail section and grouping levels. It includes the CategoryName field from
the group level, and the ProductName and UnitsInStock fields from the detail
section. These three fields will make up the child recordset for each parent
row in the parent recordset. Note from the original SQL statement that this
portion is wrapped in braces {}, and is aliased as rsLevel0.
10. Concentrate on the COMPUTE clause of the original SQL statement:
…COMPUTE rsLevel0, Count(rsLevel0.[ProductName]) AS __Agg0 BY
CategoryName AS __COLRef0)…
This portion of the statement creates the parent band in the hierarchical
recordset. The parent recordset will contain a unique row for each field specified
Product Support Services White Paper
21
after the BY clause above. In this case, because the report is grouping on
CategoryName, the parent recordset will contain 8 rows, because there are 8
unique categories in the Categories table.
The parent recordset will also contain three fields: CategoryName, __Agg0, and
rsLevel0. The CategoryName field contains the values from each unique
occurrence of that field from the underlying table. The __Agg0 field contains the
Count([ProductName]) for each band. The rsLevel0 field contains a child ADO
recordset that contains the three fields specified in the SELECT portion of the SQL
statement.
Visually, the hierarchical recordset would be represented as:
Note that the parent row contains the information that the group level contains,
whereas the child recordset contains the information specific to the detail section.
Retrieving the Report Recordset Property
After a report has been opened, you can retrieve an ADO recordset from the
report's Recordset property. However, you cannot retrieve the report's recordset
during the Open event. During this event, the report's recordset does not exist
yet. If you try to retrieve the Recordset property of a natively bound report
during the Open event, you receive the following error message:
Product Support Services White Paper
22
To retrieve the recordset, define a variable as ADODB.Recordset, and then set
the variable to the report's Recordset property. For example, this sample code
runs during the report header's Format event, retrieves the recordset, and prints
out the record count:
Private Sub ReportHeader_Format(Cancel As Integer, FormatCount As
Integer)
Dim rs As ADODB.Recordset
Set rs = Me.Recordset
rs.MoveLast
Debug.Print rs.RecordCount
Set rs = Nothing
End Sub
Conclusion
Microsoft Access 2002 offers powerful features through the improved form
Recordset property and the Client Data Manager. The form Recordset property
and the Client Data Manager provide developers with more flexibility and
improved update semantics when binding forms to ADO recordset objects as well
as the capability to bind forms to other data sources, such as Jet or Oracle
databases, through ADO.
The new Recordset property for controls allows developers to bind list box and
combo box controls to ADO recordset objects that have already been opened, in
order to minimize network traffic. Finally, the new Recordset property for
reports allows developers to bind project (.adp) reports to flat and hierarchical
ADO recordsets.
Product Support Services White Paper
23
FOR MORE INFORMATION
For the latest information about Microsoft Access see the following resources:

http://msdn.microsoft.com/office/

http://www.microsoft.com/office/access/default.htm
Product Support Services White Paper
24