Chapter 14. Programming DAO: Overview

We have seen that Access SQL provides a way to create and manipulate database objects, such as tables and queries, through its DDL and DML components. In addition, users can enter SQL statements directly into the Access SQL View window.

On the other hand, Microsoft Access allows us to program the Jet database engine directly, through its programming interface, which is known as Data Access Objects , or DAO. This gives the user far more control over a database.

DAO is a complicated structure, and I won’t discuss all of its aspects. Our focus in this book will be on gaining a general understanding of the following concepts and components:

  • The organization of DAO, which is at least partly object-oriented

  • The DDL component of DAO

  • The DML component of DAO

I will certainly not cover all aspects of the DDL and DML components. My main goal is to prepare you so that you can get whatever additional information you need from Microsoft Access’ extensive online help for the DAO model or from similar hardcopy reference manuals.

Objects

Before discussing the various components of the DAO model, we must discuss the concept of an object . In the parlance of object-orientation, an object is something that is identified by its properties and its methods(or actions).

As we will see (and as the name implies) DAO is full of objects. For example, each saved table in an Access database is an object, called a TableDefobject. (Actually, it is the definition of the table, rather than its data, that is an object of type TableDef.) Some of the properties of TableDef objects are Name, RecordCount, DateCreated, and LastUpdated.

An object’s methods can be thought of as procedures or functions that act on the object. For instance, one of the methods of a TableDef object is CreateField, which, as the name implies, is used to create a new field for the TableDef object. Another method is OpenRecordset , which creates a Recordset object that can be used to manipulate the data in the table. (A more object-oriented view of methods is that they are messages sent to the object, saying, in effect, perform the following action.)

Object Variables

In order to access the properties or invoke the methods of an object, we need to first define an object variable to reference that object.

VBA and DAO offer a wide variety of object data types . There is a slight difference in syntax when declaring and setting an object variable, as opposed to a standard variable. For instance, here is an example using the Database object type. Note that the full pathname of the LIBRARY database on my PC is d:dbaselibrary.mdb:

Dim dbLibrary as Database
Set dbLibrary = "d:dbaselibrary.mdb"

In general, the syntax is:

Dim objectVariable as ObjectDataType
Set objectVariable = ObjectName

Note that the only difference between setting object variables and setting standard variables is the keyword Set. However, this minor syntactic difference belies a much more significant difference between standard variables and object variables.

In particular, a standard variable can be thought of as a name for a location in the computer’s memory that holds the data. For instance, in the code:

Dim intVar As Integer
intVar = 123

the variable intVar is a 4-byte memory location that holds the integer value 123. Figure 14-1 illustrates the variable intVar. (Actually, the 4-byte memory location holds the value 123 in binary format, but that is not relevant to our discussion.)

An example of the intVar variable
Figure 14-1. An example of the intVar variable

Of course, if we were to write:

Dim intVar As Integer
Dim intVar2 As Integer
intvar = 123
intVar2 = intVar
intVar2 = 567

we would not expect the last line of code to have any effect upon the value of the variable intVar, which should still be 123.

On the other hand, an object variable is not the name of a memory location that holds the object’s “value,” whatever that means. Rather, an object variable holds the address of the area of memory that holds the object. Put another way, the object variable holds a reference to, or points to, the object. It is therefore called a pointer variable. The idea is pictured in Figure 14-2, where rsBooks and rsBooks2 are object variables, both pointing to an object of type Recordset.

An example of a pointer variable
Figure 14-2. An example of a pointer variable

To illustrate this further, consider the code in Example 14-1.

Example 14-1. An object variable example
Sub exaObjectVar(  )

'Declare some object variables
Dim dbLib As DATABASE
Dim rsBooks As Recordset
Dim rsBooks2 As Recordset

'Set dbLib to the current database (i.e. LIBRARY)
Set dbLib = CurrentDb

'Open a recordset object for the BOOKS table
Set rsBooks = dbLib.OpenRecordset("BOOKS")

'Two object variables will refer to the same object
Set rsBooks2 = rsBooks

'Use a property of this object
MsgBox "BOOKS record count: " & rsBooks.RecordCount

'Destroy the object using rsBooks2 reference
rsBooks2.Close

'Now rsBooks has nothing to refer to, so we get error
MsgBox "BOOKS record count: " & rsBooks.RecordCount

End Sub

First, we declare two object variables of type Recordset (we will discuss this type in detail later). The line:

Set rsBooks = dbLib.OpenRecordset("BOOKS")

sets rsBooks to point to (or refer to) a Recordset object created from the BOOKS table. Note again that, unlike standard variables, setting an object variable requires the use of the keyword Set. The line:

Set rsBooks2 = rsBooks

sets rsBooks2 to point to the same Recordset object as rsBooks, as shown in Figure 14-2.

Next, the line:

MsgBox "BOOKS record count: " & rsBooks.RecordCount

displays the message box in Figure 14-3, showing that there are 14 books in the recordset.

The message box from the exaObjectVar( ) example
Figure 14-3. The message box from the exaObjectVar( ) example

To illustrate the fact that both variables point to the same object, the line:

rsBooks2.Close

uses the pointer rsBooks2 to destroy (or close) the Recordset object. Then, when the line:

MsgBox "BOOKS record count: " & rsBooks.RecordCount

is executed, the Recordset object that both variables referred to is gone, and so the expression rsBooks.RecordCount causes an “Object invalid or no longer set” error, as shown in Figure 14-4.

Error message from the exaObjectvar( ) example
Figure 14-4. Error message from the exaObjectvar( ) example

The moral of this example is that it is important to remember that object variables refer to objects and that more than one variable can refer to the same object. Despite this, it is customary to use the misleading statement “the objVar object” when we really should be saying “the object referred to by objVar.”

Object-Variable Naming Conventions

Tables Table 14-1 and Table 14-2 describe the naming convention for both standard and object variables that we will (try to) use in this book. (Table 14-1 is a repeat of Table 10-3.) We will explain the various object types as we proceed through this chapter.

Table 14-1. Standard-variable naming for VBA

Variable

Prefix

Boolean

bool, b, or f

Byte

b, byt, or bt

Currency

cur

Date

dt or dte

Double

d or dbl

Integer

i, c, or int

Long

l, c, or lng

Single

s or sng

String

str

User-defined type

typ, u, or ut

Variant

v or var

Table 14-2. Object-variable naming for VBA

Variable

Prefix

Container

con

Database

db

Document

doc

Dynaset

dyn

Error

err

Field

fld

Form

frm

Index

idx

Object

obj

Parameter

prm

Property

prp

QueryDef

qdf

Recordset

rs

Relation

rel

Report

rpt

Snapshot

snp

Table

tbl

TableDef

tdf or tbl

User

usr

Workspace

ws

Referencing the Properties and Methods of an Object

The general syntax for referring to an object’s properties and methods is very simple. Suppose that objVar is a variable that refers to an object. If AProperty is a property of this object, then we can access this property using the syntax:

objVar.AProperty

If AMethod is a method for this object, then we can invoke that method with the syntax:

objVar.AMethod(any required parameters)

To illustrate, consider the code in Example 14-2.

Example 14-2. A property and method example
Sub exaPropertyMethod(  )

Dim dbLib As DATABASE
Dim qdfExpensive As QueryDef

' Get current database (LIBRARY)
Set dbLib = CurrentDb

' Show Name property
MsgBox dbLib.Name

' Invoke the CreateQueryDef method to create a query
Set qdfExpensive = dbLib.CreateQueryDef("Expensive",_
"SELECT * FROM BOOKS WHERE Price > 20")

End Sub

The line:

Set dbLib = CurrentDb

sets the object variable of type Database to point to the current database, that is, the LIBRARY database. The line:

MsgBox dbLib.Name

displays the value of the Name property of dbLib. The line:

Set qdfExpensive = dbLib.CreateQueryDef("Expensive",_
"SELECT * FROM BOOKS WHERE Price > 20")

invokes the CreateQueryDefmethod to create a new query named Expensive and defined by the SQL statement:

SELECT * FROM BOOKS WHERE Price > 20

Note that the code:

dbLib.CreateQueryDef("Expensive","SELECT * FROM BOOKS WHERE Price > 20")

invokes the method, which returns the QueryDef object, which is then pointed to by the object variable qdfExpensive. If you run this program, you will notice a new entry in the Query tab of the Database window. (If the query Expensive is already in the database, delete it before running this program. Also, you may need to switch away from and then return to the Query tab to refresh the list.)

The DAO Object Model

As the name Data Access Objects suggests, the DAO is, at least in part, an object-oriented environment. In particular, the DAO is implemented as a hierarchy of collections of objects. Figure 14-5 shows the DAO Object Model, describing the collections and their objects.

The DAO object model
Figure 14-5. The DAO object model

Each of the shaded boxes represents a collection of objects. (Thus DBEngine is the only noncollection.) The name of the objects contained within a given collection is just the singular of the collection name. For instance, the TableDefs collection holds TableDef objects, and the Documents collection holds Document objects. DBEngine is the only standalone object—not contained in any collection.

There is a potential point of confusion about the DAO object hierarchy in Figure 14-5 that we should address. Consider, for example, the relationship between the Databases and Workspaces collections. It would be incorrect to say, as one might infer from the diagram, that the Databases collection is contained in the Workspaces collection. Indeed, the line from Workspaces to Databases means that each Workspace object has (or as Microsoft would say, “contains”) a Databases collection.

Perhaps the best way to view the situation is to say that each object in the DAO hierarchy has three things associated with it: collections , methods , and properties. For instance, a Workspace object has the following items associated with it:

Collections
Databases
Groups
Users
Properties (not shown in Figure 14-5)
Methods
BeginTrans
Close
CommitTrans
CreateDatabase
CreateGroup
CreateUser
OpenDatabase
Rollback
Properties
IsolateODBCTrans
Name
UserName

Let us pause for a brief aside. In an object-oriented environment such as C++, or even Visual Basic, a collection is also considered an object. Moreover, the value of one object’s property can be another object (these are so-called object properties ). Hence, in such an object-oriented environment, we would probably think of the collections associated with an object as just additional properties of that object. However, Microsoft chose not to express this explicitly in the DAO.

Figure 14-6 shows a more detailed example of the object-collection relationship. The Containers collection in this case contains three Container objects, each of which has (the same) properties and methods. Each object also “contains” a Documents collection, which contains some Document objects.

A detailed example of the object-collection relationship
Figure 14-6. A detailed example of the object-collection relationship

Thus, according to this model, there may be more than one Documents collection. Indeed, there is one Documents collection for every Container object. Similarly, there is one Databases collection for each Workspace object and one TableDefs collection for each Database object.

The Microsoft Access Object Model

You may have noticed that there are no collections in the DAO object model corresponding to Access forms or reports. The fact is that DAO is not the whole object story. Microsoft Access defines its own collections of objects, as shown in Figure 14-7.

The Microsoft Access object model
Figure 14-7. The Microsoft Access object model

Access defines the Forms collection to hold all currently open forms. (Note the words “currently open.”) Similarly, the Reports collection holds all currently open reports. The Application, DoCmd, and Screen objects are not contained in a collection. The Modules collection holds all open code modules.

The References collection holds all Reference objects. A Reference object is a reference to another application’s type library, which is a file containing information on the objects that the application exposes through Automation. It is through Automation objects that an application can share some of its features with other applications. However, we will not go further into this subject in this book. (Allow me to recommend my book Concepts of Object-Oriented Programming with Visual Basic, published by Springer-Verlag, for more information on OLE Automation geared toward the Visual Basic programmer.)

As you can see in Figure 14-7, Microsoft has added several new objects to the object model for Access 9 for Office 2000. (In fact, there are a few more objects not shown in the figure.) Several of these objects relate to the Internet. The CodeData and CurrentData objects have child collections containing all tables and all queries (whether open or not). The CodeProject and CurrentProject objects have child collections containing all forms, reports, modules, macros, and DataAccessPages (whether open or not).

We will not discuss the Access object model in general in this book, since it belongs more to issues related to the Access user interface (forms and reports) than to database manipulation.

On the other hand, we will discuss some aspects of the Access object model. For instance, the line:

Set db = CurrentDb

sets the variable db to point to the currently open database. The function CurrentDb, which we will discuss in more detail later, is not a DAO function—you will not find it in the DAO reference manual. It is a part of the Access object model: it is a method of the Application object, to be precise. Thus, the Access object model and DAO both provide supporting objects and instructions for database management.

Referencing Objects

The first step in understanding the objects in the DAO and Microsoft Access object hierarchies is to understand how to refer to an object in the hierarchy. In particular, we can refer to an object by the name of ObjectName that belongs to a collection named CollectionName, by any of the following syntaxes:

  • CollectionName!ObjectName, or CollectionName![ObjectName] when ObjectName has illegal characters, such as spaces.

  • CollectionName("ObjectName").

  • CollectionName(StringVar), where StringVar holds the string ObjectName.

  • CollectionName(Index), where Index is the index number of the object in the collection. Indexes start with 0 and go up to one less than the number of objects in the collection. (As we will see, the number of elements in a collection is denoted by CollectionName.Count.)

For instance, the TableDef object named BOOKS in the TableDefs collection is denoted by:

TableDefs!BOOKS

or:

TableDefs("BOOKS")

or:

Dim strBooks as String
strBooks = "BOOKS"
TableDefs(strBooks)

or, if BOOKS happens to be the first TableDef object in the TableDefs collection:

TableDefs(0)

The exclamation point (!) used in the first syntax is called the bang operator .

Fully Qualified Object Names

There is a problem with these names. For instance, to which object does Fields(0) refer? There are several Fields collections in the DAO hierarchy, as can be seen from Figure 14-5. Let us refer to the names described in the previous syntax as semiqualified names. To avoid the problem that a semiqualified name may not be unique, we must use the fully qualified object name, which is formed by tracing the entire hierarchy from the top (DBEngine) to the desired object. For instance, the fully qualified name for BOOKS is:

DBEngine.Workspaces(0).Databases![d:dbaselibrary.mdb].TableDefs!BOOKS

Let us examine this name. It is composed of four separate semiqualified object names, separated by periods. These periods are referred to as dot operators :

DBEngine.
Workspaces(0).
Databases![d:dbaselibrary.mdb].
TableDefs!BOOKS

Perhaps the easiest way to make sense of this name is to start from the bottom. The semiqualified name of the object we are interested in is:

TableDefs!BOOKS

This object is contained in the TableDefs collection for the Database object named:

Databases![d:dbaselibrary.mdb]

This object is, in turn, contained in the Databases collection of the default Workspace object (more on this later), which is:

Workspaces(0)

which, in turn, is contained in the DBEngine object. Separating each of these object names by the dot operator gives the fully qualified object name.

In general, the syntax for a semiqualified object name is:

Collection!Object

and for a fully qualified object name, it is:

DBEngine.Collection1!Object1. · · · .CollectionN!ObjectN

There seems to be much confusion over when to use the bang operator (!) and when to use the dot operator (.). Perhaps the following will help:

  • The bang operator is used to separate an object’s name from the name of the collection of which it is a member. In other words, bang signifies a member of a collection. It therefore appears in semiqualified object names.

  • The dot operator is used to separate each semiqualified object name in a fully qualified object name. In other words, it signifies the next step in the hierarchy.

  • The dot operator is also used to denote a property or method of an object.

This naming convention is really not as confusing as it may look at first, if you remember the previous three maxims. However, if you want confusing, stay tuned for default collections.

Using Object Variables to Your Advantage

As you can see, a fully qualified object name can be quite lengthy. This problem is compounded by the fact that it may be necessary to refer to the same object many times in a program. There are two common ways to deal with this issue.

One way is to use object variables. Consider the code in Example 14-3 to display the RecordCount property of the BOOKS table.

Example 14-3. An object variable example
Sub exaObjVar(  )

Dim ws As Workspace
Dim dbLib As DATABASE
Dim tdfBooks As TableDef

Set ws = DBEngine.Workspaces(0)
Set dbLib = ws.Databases![d:dbaselibrary.mdb]
Set tdfBooks = dbLib.TableDefs!BOOKS

MsgBox tdfBooks.RecordCount

End Sub

By defining three object variables, ws, dbLib , and tdfBooks , we were able to avoid writing the fully qualified name of BOOKS (on a single line, that is). Also, the line:

MsgBox tdfBooks.RecordCount

is much easier to read. (It reads: “Message me the record count of TableDef tdfBooks.”)

The use of object variables in this way has several advantages and is highly recommended. First, it tends to make the lines of code shorter and more readable. Second, we can refer to the object variable tdfBooks many times without having to write the fully qualified object name each time. As a result, the program will run somewhat faster, since VBA does not have to resolve the object name by climbing down the object hierarchy more than once.

Default Collections

There is another method that can be used for shortening fully qualified object names. In particular, each object has a default collection , which can be used as follows. Consider a portion of a fully qualified name:

Collection1!Object1.Collection2!Object2

If Collection2 is the default collection of Object1, then this name may be shortened to:

Collection1!Object1!Object2

where we have omitted the default collection name Collection2, as well as the preceding dot.

For instance, the default collection of DBEngine is Workspaces. Hence:

DBEngine.Workspaces!MyWorkspace

can be shortened to:

DBEngine!MyWorkspace

and the phrase:

DBEngine.Workspaces(0)

can be shortened to:

DBEngine(0)

Also, since the default collection for a Workspace object is Databases, the phrase:

DBEngine.Workspaces(0).Databases(0)

can be shortened to:

DBEngine(0)(0)

Table 14-3 shows the default collections in the DAO and Access object model.

Table 14-3. DAO and Access object default collections

Object

Default collection

DBEngine

Workspaces

Workspace

Databases

Database

TableDefs

TableDef

Fields

Recordset

Fields

QueryDef

Parameters

Index

Fields

Relation

Fields

Container

Documents

User

Groups

Group

Users

Forms

Controls

Reports

Controls

The use of default collections can save space. However, it does very little for readability (to say the least) and is probably best left to programmers with so much experience that they hardly read the names anyway! To emphasize the point, each of the lines in Example 14-4 displays the RecordCount property of the BOOKS table. Note that the full name of the database library file on my computer is d:dbaselibrary.mdb.

Example 14-4. A default collections example
Sub exaDefaultCollections(  )

MsgBox DBEngine.Workspaces(0).Databases![d:dbaselibrary.mdb]. _
TableDefs!BOOKS.RecordCount

MsgBox _
DBEngine(0).Databases![d:dbaselibrary.mdb].TableDefs!BOOKS.RecordCount

MsgBox DBEngine(0)![d:dbaselibrary.mdb].TableDefs!BOOKS.RecordCount

MsgBox DBEngine(0)![d:dbaselibrary.mdb]!BOOKS.RecordCount

MsgBox DBEngine(0)(0)!BOOKS.RecordCount

End Sub

Collections Are Objects Too

In a true object-centric environment, everything is an object. While Access, VBA, and DAO may not go this far, it is true that collections are objects, and so they have their own properties and methods.

In the Access environment, collections can be divided into three types:

Microsoft Access collections

Which are part of the Access object hierarchy

DAO collections

Which are part of the DAO hierarchy

User-defined collections

Which are VBA objects of type Collection

Note that only user-defined collections are of type Collection, which is a VBA data type, not a DAO data type. The properties and methods of collections are not very complicated, so let us list them here.

Properties and Methods of Access Collections

The Access collections Forms, Reports, and Controls have no methods and only one property, Count , which reports the number of objects in the collection. Thus, the line:

Forms.Count

reports the number of opened forms in the current database. (When we discuss Container objects, we’ll see that there is a way to get the number of saved forms as well.)

Properties and Methods of DAO Collections

DAO collections fall into two categories with respect to their properties and methods. All DAO collections have a single property: Count. All DAO collections also have the Refresh method, which we will discuss a bit later. In addition, some of the collections have the Append and corresponding Delete methods, while others do not.

Collections that have Append and Delete methods:
Workspaces
TableDefs
QueryDefs
Groups
Users
Relations
Fields
Indexes
Properties (explained later)
Collections that do not have Append and Delete methods:
Databases
Errors
Recordsets
Containers
Documents
Parameters

Evidently, some collections do not have Append or Delete methods because DAO does not want the user to append or delete objects from these collections. This is reasonable because DAO takes care of collection housekeeping automatically for these collections. For example, DAO automatically appends new databases to the Databases collection whenever they are created using the CreateDatabase method. However, it does not do so for new TableDef or QueryDef objects, for instance.

Note that Microsoft Access will do the housekeeping chores for you when objects are created and saved using the Access interface.

Properties and Methods of User-Defined Collections

User-defined Collection objects have one property: Count. They have three methods: Add , Remove , and Item. Add and Remove perform as advertised by their names, and we will see an example shortly. The Item method is used to identify the items in the collection, since they may or may not have names.

A single user-defined collection can contain objects of various types, including other collections. Here is an example to illustrate the Add method.

In Example 14-5, we create two collections: colParent and colChild. We then place colChild inside colParent, along with the BOOKS TableDef object. Thus, the colParent collection contains two objects of very different types—one Collection object and one TableDef object. (While this example is not of much practical value, it does illustrate the point.)

Example 14-5. A collections example
Sub exaCollections(  )

' Declare two variables of type collection
Dim colParent As New Collection
Dim colChild As New Collection

Dim tdfBooks As TableDef
Dim objVar As Object

Set tdfBooks = DBEngine(0)(0).TableDefs!Books

' Use Add method of collection object
' to add objects to colParent collection
colParent.Add colChild
colParent.Add tdfBooks

' Display size of collection
MsgBox "Size of Parent collection " & colParent.Count

' Iterate through collection. Note use of
' TypeOf statement
For Each objVar In colParent
    If TypeOf objVar Is Collection Then
        MsgBox "Collection"
    ElseIf TypeOf objVar Is TableDef Then
        MsgBox objVar.Name
    End If
Next

End Sub

In Example 14-5, we used the Add method of the Collection object to add items to the collection and the Count property of the Collection object, which returns the size of the collection. Note also the use of the TypeOf statement to determine the type of each object in the collection.

Now let us consider the Item method, which returns a specific object from a collection. The general syntax is:

Collection.Item(index)

where index is an index into the collection. Note that DAO collections begin with index 0 and go to index Collection.Count - 1.

To illustrate the Item method, in place of the code:

For Each tbl In db.TableDefs
    strTbls = strTbls & vbCrLf & tbl.Name
Next tbl

we could have written:

For i = 0 To db.TableDefs.Count - 1

  strTbls = strTbls & vbCrLf & _
db.TableDefs.Item(i).Name

Next i

We should remark that an object’s ordinal position in a collection is never guaranteed and can sometimes change without warning. Thus, for example, it is unwise to rely on the fact that the object that is Item(0) at some time will always be Item(0).

Incidentally, one of the drawbacks of collections that contain different types of objects, as in the previous example, is that we can seldom do the same thing to all of the objects in the collection. For this reason, creating collections containing different types of objects is generally not very useful.

Say It Again

It is worth re-emphasizing that the collections in the DAO hierarchy are not contained in their parent collections (as is the case for the user-defined collections in the previous example). For example, the TableDefs collection contains only TableDef objects (table definitions). It does not contain the Fields collection. Rather, each TableDef object contains a Fields collection. We can confirm this with the code in Example 14-6, which displays the size of the TableDefs collection for the LIBRARY database as 14 and then displays the names of each of its 14 objects, showing that there is nothing but TableDef objects in the TableDefs collection.

Example 14-6. A TableDef example
Sub exaCheckTableDefs(  )

Dim db As DATABASE
Dim tbl As TableDef
Dim strTbls As String

Set db = CurrentDb

strTbls = ""
MsgBox db.TableDefs.Count
For Each tbl In db.TableDefs
    strTbls = strTbls & vbCrLf & tbl.Name & " - " & TypeName(tbl)
Next

MsgBox strTbls

End Sub

Running the code in Example 14-6 produces two message boxes; the second is shown in Figure 14-8, which also shows that most of the TableDefs in the database are system-table definitions, created by Microsoft Access for its own use. ( Just in case some additional tables get added to the LIBRARY database after this book goes to print, you may find a different list of tables when you run this example.) Figure 14-8 also illustrates the use of the function TypeName.

A list of TableDefs generated by exaCheckTableDefs( )
Figure 14-8. A list of TableDefs generated by exaCheckTableDefs( )

Refreshing Certain Collections

There are times when the Microsoft Jet engine does not have the latest information on the contents of a collection. For example, this can happen in a multiuser environment, when one user makes a change to a collection. It can also happen when a host environment, such as Microsoft Access, makes a change to the environment. To see this, try the following simple experiment.

Enter the following code:

Sub temp(  )

Dim db As DATABASE
Set db = DBEngine(0)(0)

' db.TableDefs.Refresh
MsgBox "Table count: " & db.TableDefs.Count

End Sub

Run the procedure. You should get a message that there are 13 tables in the TableDefs collection. Now use Microsoft Access to create a new table, and save the table. Then rerun the previous code. It will still report that there are 13 tables! Now remove the comment mark on the line:

' db.TableDefs.Refresh

and rerun the code. You should now get an accurate table count.

The point here is that the Jet engine does not keep track of the machinations of its host application—Microsoft Access. Hence, to be certain that a collection is up to date, you may need to use the Refresh method.

The Properties Collection

One item that has been left out of the diagram of the DAO object model shown earlier in Figure 14-5 (and is done so in most DAO diagrams) is the Properties collection. This is because every DAO object has a Properties collection, so it would clutter up the diagram considerably without adding much information. Figure 14-9 shows a Properties collection.

An Access properties collection diagram
Figure 14-9. An Access properties collection diagram

The purpose of the Properties collections is simple. Properties are objects too, and so they are contained in collections, just like all other objects of the DAO (except DBEngine). Thus, the Properties collection of an object contains the Property objects (better known simply as properties) for the object.

The fact that the properties of an object are themselves objects and thus reside in a collection, implies that we may access these properties in several different ways. For example, the RecordCount property of the BOOKS TableDef object can be referred to in any of the following ways (among others):

TableDefs!BOOKS.Properties!RecordCount
TableDefs("BOOKS").Properties("RecordCount")

or just:

TableDefs!BOOKS.RecordCount

Of course, the latter form is the simplest and most commonly used. Note that the Properties collection is never the default collection for any object. Hence, for example, the syntax:

TableDefs!BOOKS!RecordCount

(which differs from the previous only by a bang) will cause VBA to look for the RecordCount object in the default Fields collection for the BOOKS TableDef object. Of course, it will not find such an object and so the error message “Item not found in this collection” will result.

The Virtues of Properties Collections

There are several virtues to the existence of Properties collections. One is that it is possible to iterate through all of the properties of an object, using the For Eachsyntax discussed earlier, for instance, without even knowing the names of the properties.

For example, the following simple code:

Dim db As DATABASE
Dim prp As Property
Set db = CurrentDb

For Each prp In db.TableDefs!BOOKS.Properties
    Debug.Print prp.Name
Next prp

produces the following list of all properties of the BOOKS object:

Name
Updatable
DateCreated
LastUpdated
Connect
Attributes
SourceTableName
RecordCount
ValidationRule
ValidationText
ConflictTable
OrderByOn
OrderBy

Another virtue of Properties collections is that they allow for the creation (and storage) of new properties. We discuss this next.

Types of Properties

In general, the properties of an object can be classified into three groups, depending upon their origin:

  • Built-in properties

  • Application-defined properties

  • User-defined properties

The Jet database engine defines built-in properties for its objects. For instance, a TableDef object has a built-in Name property. In addition, Microsoft Access (and other applications that may be using the Jet engine) can create application-defined properties. For example, if you create a table in Microsoft Access and fill in the Description field in the View...Properties dialog box, Access creates a Description property for the table and appends it to the Properties collection for that TableDef object. Finally, as we will see later, the user can create his own properties.

It is important to note that an application-defined property is created only if the user assigns a value to that property. For example, if you do not specifically type a description in the Description field, as discussed earlier, then Access will not create a Description property. In other words, Access does not create a blank Description property. If you then use this property in your code, an error will result. Thus, when you write programs that refer to either application-defined or user-defined properties, it is important to check for errors, in case the referenced property does not exist.

Of course, each Property object, being an object, has its own properties, but you will be glad to hear that these properties do not have Property objects. (Where would this end?)

We should also mention that properties can be classified as read/write, read-only, or write-only. A read/write property can be both read and written to (i.e., changed), whereas a read-only property can be read but not changed, and a write-only property can be changed but not read. When an object is first created, its read/write properties can be set. However, in many cases, once the object is appended to a collection, some of these properties may become read-only and can therefore no longer be changed.

The properties of a Property object are described as follows. A Property object has no methods.

Property: Inherited

For the built-in Property objects, this value is always 0 (False). For user-defined properties, this value is true if the property exists because it was inherited from another object. For instance, any Recordset object that is created from a QueryDef object inherits the QueryDef ’s properties.

Property: Name

The usual Name property, which in this case is the name of the property represented by this Property object.

Property: Type

This value gives the data type of the object. Note that the Type property is read/write until the Property object is appended to a Properties collection, after which it becomes read-only. The value of the Type property is an integer. VBA provides built-in constants so that we do not need to remember integer values. Table 14-4 gives these values, along with their numerical values, which are returned in code such as MsgBox Property.Type.

Table 14-4. Constants for the Type property in VBA

Data type

Constant

Numerical value

Boolean

dbBoolean

1

Byte

dbByte

2

Integer

dbInteger

3

Long

dbLong

4

Currency

dbCurrency

5

Single

dbSingle

6

Double

dbDouble

7

Date/Time

dbDate

8

Text

dbText

10

Long Binary (OLE Object)

dbLongBinary

11

Memo

dbMemo

12

GUID

dbGUID

15

Property: Value

Finally, we get to the main property of a Property object—its value, which can be any value commensurate with the assigned Type property of the Property object.

Let us consider another example of how to use the Properties collection. The code in Example 14-7 will display the entire contents of the Properties collection for the BOOKS TableDef object in the LIBRARY database.

Example 14-7. A Properties collection example
Sub exaProperties(  )

Dim db As DATABASE
Dim tbl As TableDef
Dim prp As Property
Dim str As String

Set db = CurrentDb
Set tbl = db!BOOKS

str = ""
For Each prp In tbl.Properties
    
    str = str & prp.Name
    str = str & " = " & prp.Value
    str = str & " (" & prp.Type & ") "
    str = str & prp.Inherited & vbCrLf

Next prp

MsgBox "BOOKS has " & tbl.Properties.Count _
& " properties: " & vbCrLf & str

End Sub

Running this procedure gives the window shown in Figure 14-10, where each line has the form Name = Value (Type) Inherited.

Window generated from executing exaProperties
Figure 14-10. Window generated from executing exaProperties

User-Defined Properties

We mentioned that a user can add user-defined properties to an object. Let us consider an example of adding a new property to the BOOKS TableDef object.

The code in Example 14-8 adds the user-defined property named UserProperty to the BOOKS table. It uses the CreateProperty method of the TableDef object.

Example 14-8. A user-defined properties example
Sub exaUserDefinedProperty(  )

' Add user-defined property to BOOKS TableDef object

Dim db As DATABASE
Dim tbl As TableDef
Dim prp As Property

Dim str As String

Set db = CurrentDb
Set tbl = db!BOOKS

' Create new property using CreateProperty method
Set prp = tbl.CreateProperty("UserProperty", dbText,"Programming DAO is fun.")

' Append it to Properties collection
tbl.Properties.Append prp

' List all properties
str = ""
For Each prp In tbl.Properties
    str = str & prp.Name
    str = str & " = " & prp.Value
    str = str & " (" & prp.Type & ") "
    str = str & prp.Inherited & vbCrLf
Next prp

MsgBox "BOOKS has " & tbl.Properties.Count & " properties: " & vbCrLf & str

End Sub

This procedure produces the window shown in Figure 14-11. Note the last property on the list.

Window generated from executing exaUserDefinedProperty
Figure 14-11. Window generated from executing exaUserDefinedProperty

Closing DAO Objects

We should make a few remarks about closing DAO objects that have been opened programmatically. The Database, Recordset, and Workspace objects each have a Close method. This method will remove these objects from their respective collections. This is appropriate for the three object types mentioned previously for the following reasons:

  • The Databases collection is defined to be the collection of all open database objects.

  • The Recordset objects are temporary objects, to be used only for data-manipulation purposes.

  • Attempts to close the default Workspace object are ignored, but you can close other Workspace objects.

Note that objects of types other than the three mentioned are intended to be persistent members of their collections, stored on disk in the Access mdb file. However, they can be removed from their respective collections by using the Delete method.

Here are some caveats to keep in mind with respect to closing objects:

  • As we will see in Chapter 16, you should update (i.e., complete) all pending edits before closing an open Recordset object.

  • When a procedure that declares a Recordset or Database object is exited, the recordset or database is closed, and any unsaved changes or pending edits are lost.

  • If you close a Database object while any Recordset objects are still open, or if you close a Workspace object while any of its Database objects are open, those Recordset objects will be automatically closed, and any pending updates or edits will be lost.

A Look at the DAO Objects

Now we can look briefly at each of the collections (and their objects) in the DAO Object Model. I will discuss each object and mention a few of the more commonly used properties and methods. A complete list of all collections, methods, and properties of each object is given in Appendix A.

DBEngine Object

The DBEngine object, of which there is only one, represents the Jet database engine. This is the only object in the DAO that is not contained in a collection. We have seen several examples of its use, along with the fact that the default collection for the DBEngine object is Workspaces, and so:

DBEngine.Workspaces(0)

is equivalent to:

DBEngine(0)

We have also seen that:

DBEngine(0)(0)

denotes the first database in the first (default) workspace.

The DBEngine object has methods to create a new workspace (CreateWorkspace), to compact a database (CompactDatabase), and to repair a database (RepairDatabase), among others.

Errors

From time to time, an operation may cause one or more errors to occur (or so I am told). When this happens, the Errors collection is first emptied and then filled with one Error object for each error that the operation caused. (Some operations may cause more than one error.) Note that if no errors occur, the Errors collection remains as it was before the operation.

Example 14-9, which deliberately produces an error, illustrates the use of the Errors collection. It also demonstrates the use of three Error object properties: Number (the VBA error number), Description (a description in words of the error), and Source (the object or application that generated the error).

Example 14-9. An Errors collection example
Sub exaErrorsCollection(  )
    
' Note declaration of object variable of type Error
Dim dbsTest As DATABASE
Dim txtError As String
Dim errObj As Error

On Error GoTo ehTest

' A statement that produces an error
Set dbsTest = _
DBEngine.Workspaces(0).OpenDatabase("NoSuchDatabase")

Exit Sub

ehTest:

txtError = ""
' Loop through the Errors collection,
' to get the Number, Description and Source
' for each error object
For Each errObj In DBEngine.Errors
    txtError = txtError & Format$(errObj.Number)
    txtError = txtError & ": " & errObj.Description
    txtError = txtError & " (" & errObj.Source & ")"
    txtError = txtError & vbCrLf
Next
    
MsgBox txtError 

Exit Sub

End Sub

Running this code produces the window in Figure 14-12.

Error message from executing exaErrorsCollection
Figure 14-12. Error message from executing exaErrorsCollection

Workspaces

There is one Workspace object for each Access user session. In a single-user environment, there is generally only one session running. When a user starts Access with no security options enabled, Access automatically creates a Workspace called:

DBEngine.Workspaces(0)

Since we are not concerned in this book with multiple users or with database-security issues, we will not be creating multiple workspaces.

The values of the Name and UserName properties of the default Workspace object are easily determined by running the following code:

Sub Test(  )

MsgBox "Count: " & DBEngine.Workspaces.Count
MsgBox "Name: " & DBEngine.Workspaces(0).Name
MsgBox "UserName: " & DBEngine.Workspaces(0).UserName

End Sub

This code should produce three message boxes, indicating that there is only one open workspace, with name #Default Workspace# and username admin.

Among the methods of a Workspace object are CreateDatabase (for creating a new database) and OpenDatabase (for opening an existing database). Another interesting group of methods is BeginTrans, CommitTrans , and Rollback, which allow the programmer to group several operations into one transaction. At the end of the transaction, the programmer can commit the operations—or rollback the database to its state prior to any of the operations in the transaction. One use for this is in updating related tables (as in transferring money from one table to another). If the entire group of operations is not completed successfully, then a rollback is probably desirable.

Workspace objects also have a Close method for closing opened workspaces. However, the method is ignored when applied to the default Workspace under Microsoft Access.

Users

The Jet engine provides security by assigning access permissions to users of the engine. A User object represents a user of the Jet engine. The Users collection contains all User objects. (Of course, female users are never to be considered objects.)

Groups

A Group object represents a set of User objects (users) that have a common set of access permissions. By using Group objects, a new user can be given a set of access permissions simply by adding the corresponding User object to the appropriate Group object. The Groups collection holds all Group objects.

Databases

A Database object represents a currently open database. In Microsoft Jet, you can have multiple databases open at one time (using the OpenDatabase function, discussed in Chapter 15). However, the Microsoft Access environment can display a graphical interface for only one database. In the Microsoft Access environment, when a database is opened, it is assigned to DBEngine.Workspaces(0).Databases(0).

Database objects have a variety of methods for creating new objects: CreateProperty, CreateQueryDef, CreateTableDef , and OpenRecordset. There is also an Execute method for running action queries or executing SQL statements on the database. As mentioned earlier, Database objects also have a Close method.

TableDefs

A TableDef object represents a table definition for a saved table in the database. A TableDef object is more than a table scheme, in that it also has a RecordCount property that gives the number of rows in the table (and thus, in some sense, reflects the data in the table). However, it is less than a table, in that it does not describe the actual data in the table. The TableDefs collection contains all TableDef objects for a given database. TableDef objects have methods for creating fields (CreateField), indexes (CreateIndex), and opening recordsets (OpenRecordset).

QueryDefs

A QueryDef object represents a saved query in the database. The QueryDefs collection contains all QueryDef objects for a given database. One of the most interesting properties of a QueryDef object is SQL, which can be used to set or read the SQL definition of the QueryDef object.

Recordsets

A Recordset object represents data from one or more tables or queries, and is used to manipulate that data. Note that a Recordset object is temporary, in that it is not saved with the application. In fact, recordsets are created in code using the OpenRecordset function. The Recordsets collection contains all open Recordset objects in the current database.

Recordset objects are the workhorses of the DAO object model, with about 15 different methods and about 20 different properties. There are actually three types of Recordset objects—Table-type, Dynaset, and Snapshot—used for different purposes. We will discuss recordsets in Chapter 15.

Relations

A Relation object represents a relationship between certain fields in tables or queries. The Relation object can be used to view or create relationships. The Relations collection contains all Relation objects for a given database. We will discuss how to create a relation in .

Containers

The Microsoft Jet engine provides the Containers collection as a location where a host application, such as Microsoft Access, can store its own objects. This is done through the use of Container objects, as shown in Figure 14-13.

Container objects diagram of the MS Jet engine
Figure 14-13. Container objects diagram of the MS Jet engine

The Jet engine itself creates three Container objects:

  • A Databases container object, containing information about the database

  • A Tables container object, containing information about each saved table and query

  • A Relations container object, containing information about each saved relationship

It is important not to confuse these Container objects (which are not collections, despite their names) with the Databases, TableDefs, and Relations collections. Indeed, these objects are at entirely different locations in the DAO object hierarchy and serve different purposes, as we will see.

In addition to the Container objects created by the Jet engine, Microsoft Access stores its forms, reports, macros, and modules in the Containers collection. Hence, the Containers collection also contains:

  • A Forms container object, containing information about all saved forms

  • A Reports container object, containing information about all saved reports

  • A Macros container object, containing information about all saved macros

  • A Modules container object, containing information about all saved modules

The Forms and Reports Container objects should not be confused with the Microsoft Access collections of the same name (in the Access object model). In particular, the former contains information about all saved objects, whereas the latter contains information about all open objects.

To illustrate the aforementioned difference, create and save two forms in an Access session, and make sure that only one form is open. Then run the code in Example 14-10, which should report that the open form count is 1 but the saved form count is 2.

Example 14-10. A Containers collection example
Sub exaFormsContainer(  )

Dim db As DATABASE
Dim frm As Form
Dim doc As Document

Set db = CurrentDb

Debug.Print "Opened form count: " & Forms.Count
For Each frm In Forms
    Debug.Print frm.Name
Next
Debug.Print

Debug.Print "Saved form count: " & db.Containers!Forms.Documents.Count
For Each doc In db.Containers!Forms.Documents
    Debug.Print doc.Name
Next

End Sub

Note that a user cannot create new or delete existing Container objects—they are controlled by the Jet engine only. Put another way, there is no such thing as a user-defined Container object. The properties of a Container object generally reflect security-related issues, such as permission and user/group names. Container objects have no methods.

Documents

We have seen that applications (including Jet and Access) store objects through the use of Container objects. However, the Forms Container object, for example, is not of any real interest per se. The Form objects that reside within the Forms container are of interest. Actually, these Form objects are referred to as Document objects and are contained in the Documents collection of the Forms container, also shown in Figure 14-6. (If you are getting a bit confused, Figure 14-6 should help—it always helps me.)

Thus, it is the Document objects (in a Documents collection) that are the raison d'être for the Container objects. Example 14-11 illustrates a few of the properties of a Document object: Container, DateCreated, LastUpdated, Name, and Owner. It displays the value of various properties of the Document objects in the Documents collection of the Tables Container object.

Example 14-11. Properties of the Document object
Sub exaTablesDocuments(  )
Dim db As DATABASE
Set db = CurrentDb
Dim docs As Documents
Dim doc As Document

Set docs = db.Containers!Tables.Documents
Debug.Print "Count: " & docs.Count

For Each doc In docs

    Debug.Print "Container: " & doc.Container
    Debug.Print "DateCreated: " & doc.DateCreated
    Debug.Print "LastUpdated: " & doc.LastUpdated
    Debug.Print "Name: " & doc.Name
    Debug.Print "Owner: " & doc.Owner
    Debug.Print

Next doc

End Sub

Here is a portion of the output from executing Example 14-11:

Count: 16 
Container: Tables
DateCreated: 10/22/96 3:16:44 PM
LastUpdated: 10/24/96 1:36:16 PM
Name: AUTHORS
Owner: admin

Container: Tables
DateCreated: 10/22/96 3:19:47 PM
LastUpdated: 10/24/96 1:36:16 PM
Name: BOOK/AUTHOR
Owner: admin

Container: Tables
DateCreated: 5/15/96 6:16:29 PM
LastUpdated: 5/15/96 6:16:29 PM
Name: MSysACEs
Owner: Engine

Container: Tables
DateCreated: 5/15/96 6:16:31 PM
LastUpdated: 5/15/96 6:16:31 PM
Name: MSysIMEXColumns
Owner: admin

Fields

The Fields collection contains Field objects, which describe the various fields in a TableDef, QueryDef, Index, Relation, or Recordset object.

Parameters

The parameters of a parameter query are represented by Parameter objects, contained in the Parameters collection for that QueryDef object. Note that Parameter objects cannot be added to or deleted from the Parameters collection—Parameter objects represent existing parameters. Let us consider an example.

The code in Example 14-12 creates a parameter query named ParameterQuery and demonstrates some of the properties of a Parameter object—namely, Name, Type, and Value.

Example 14-12. A parameter query example
Sub exaParameters(  )
Dim db As DATABASE
Dim qdf As QueryDef
Dim strSQL As String

Set db = CurrentDb

' Create an SQL statement with parameters
strSQL = "SELECT * FROM BOOKS WHERE _
Price > [Enter minimum price]"

' Create a new QueryDef object
Set qdf = db.CreateQueryDef("ParameterQuery", strSQL)
    
' Supply value for parameter
qdf.PARAMETERS![Enter minimum price] = 15

' Now query query
Debug.Print qdf.PARAMETERS![Enter minimum price].Name
Debug.Print qdf.PARAMETERS![Enter minimum price].Type
Debug.Print qdf.PARAMETERS![Enter minimum _
price].Value

End Sub

Indexes

An Indexes collection contains all of the saved Index objects (i.e., indexes) for a TableDef object. We will discuss how to create an index in Chapter 15.

The CurrentDb Function

We have seen that DAO refers to the current database as:

DBEngine.Workspaces(0).Databases(0)

or, through default collections, as:

DBEngine(0)(0)

However, within Microsoft Access, there is a preferred way to refer to this database, since, unlike DBEngine(0)(0), it is always current with respect to changes made using the Access graphical interface. This preferred way is to use the Access function CurrentDb. Unfortunately, there is some confusion as to precisely what this function does.

Here is part of what the Access help system says about this function:

The CurrentDb function returns an object variable of type Database that represents the database currently open in the Microsoft Access window.

The CurrentDb function provides a way to access the current database from Visual Basic code without having to know the name of the database. Once you have a variable that points to the current database, you can also access and manipulate other objects and collections in the data access object hierarchy.

You can use the CurrentDb function to create multiple object variables that refer to the current database. In the following example, the variables dbsA and dbsB both refer to the current database:

Dim dbsA As Database, dbsB As Database
Set dbsA = CurrentDb
Set dbsB = CurrentDb

This certainly makes it appear as though the object variables dbsA and dbsB point to a single Database object, namely, the currently open database. In other words, executing the instruction:

Set db = CurrentDb

implies that db points to the Database object known to DAO as DBEngine(0)(0). However, the Help system goes on to say:

Note: In previous versions of Microsoft Access, you may have used the syntax DBEngine.Workspaces(0).Databases(0), or DBEngine(0)(0) to return a pointer to the current database. In Microsoft Access for Windows 95, you should use the CurrentDb function instead. The CurrentDb function creates another instance of the current database, while the DBEngine(0)(0) syntax refers to the open copy of the current database. Using the CurrentDb function enables you to create more than one variable of type Database that refers to the current database. Microsoft Access still supports the DBEngine(0)(0) syntax, but you should consider making this modification to your code in order to avoid possible conflicts in a multiuser database.

This seems to contradict the previous statements, by indicating that each time CurrentDb is executed, it creates a new Database object. Actually, if the current database is considered an object, then the statement “...creates another instance of the current database...” makes no sense, since you cannot create an instance of an object. (In object-oriented terms, you can create an instance of a class, and such an instance is called an object.)

In any case, each call to CurrentDb does seem to create a new object, as we can see from the experiment in Example 14-13, which checks the Count property of the Databases collection both before and after calling CurrentDb, showing that the count goes up.

Example 14-13. A CurrentDb function example
Sub exaCurrentDB(  )

Dim db, dbExtra, dbOriginal As DATABASE
Dim str As String
Dim i As Integer

Set dbOriginal = DBEngine(0)(0)

' Check the database count
MsgBox "Initial db count: " & _
DBEngine.Workspaces(0).Databases.Count

' Invoke CurrentDB
Set dbExtra = CurrentDb(  )

' Check the database count again
MsgBox "Count after CurrentDb run: " & _
DBEngine.Workspaces(0).Databases.Count

' Display the two database names
str = ""
For Each db In DBEngine.Workspaces(0).Databases
    str = str & vbCrLf & db.Name
Next db
MsgBox "Db Names: " & vbCrLf & str

dbExtra.Close

End Sub

If each call to CurrentDb produces a pointer to a new object, then it is natural to wonder what happens if we change the object pointed to by one of these pointers. Does it affect the other objects? What about DBEngine(0)(0)? Consider the code in Example 14-14, which does the following:

  • Creates two Database object variables dbOne and dbTwo and sets both equal to CurrentDb

  • Adds a new field NewField1 to the BOOKS table using dbOne

  • Adds a new field NewField2 to the BOOKS table using dbTwo

  • Displays the list of fields for BOOKS using dbOne

  • Displays the list of fields for BOOKS using dbTwo

  • Closes dbOne and dbTwo; that is, it removes their objects from the Databases collection

     
Example 14-14. The dbOne and dbTwo variable example
Sub exaCurrentDb2(  )

Dim dbOne As Database, dbTwo As DATABASE
Dim fldNew As Field
Dim str As String

Set dbOne = CurrentDb
Set dbTwo = CurrentDb

' Get field list in BOOKS
str = "Fields before: " & vbCrLf
''MsgBox dbOne.TableDefs!Books.Fields.Count
For Each fldNew In dbOne.TableDefs!Books.Fields
    str = str & fldNew.Name & vbCrLf
Next

' Use dbOne to add a new field to BOOKS
Set fldNew = dbOne.TableDefs!Books.CreateField("NewField1", dbInteger)
dbOne.TableDefs!Books.Fields.Append fldNew

' Use dbTwo to add a new field to BOOKS
Set fldNew = dbTwo.TableDefs!Books.CreateField("NewField2", dbInteger)
dbTwo.TableDefs!Books.Fields.Append fldNew

''Stop - (see the explanation in the text)

' Refresh Fields collection using dbOne!!!
dbOne.TableDefs!BOOKS.Fields.Refresh

' Get field list now using dbOne
str = str & vbCrLf & "Fields after using dbOne: " & vbCrLf
For Each fldNew In dbOne.TableDefs!Books.Fields
    str = str & fldNew.Name & vbCrLf
Next

' Get field list now using dbTwo
str = str & vbCrLf & "Fields after using dbTwo: " & vbCrLf
For Each fldNew In dbTwo.TableDefs!Books.Fields
    str = str & fldNew.Name & vbCrLf
Next

MsgBox str

dbOne.Close
dbTwo.Close

End Sub

Running this code produces the window shown in Figure 14-14.

Message box from executing exaCurrentDb2
Figure 14-14. Message box from executing exaCurrentDb2

Thus, it appears that changing the Database object pointed to by dbTwo does in fact also change the Database object pointed to by dbOne. However, if we do not refresh the Fields collection using the variable dbOne, or if we refresh using the variable dbTwo instead, we get the message box shown in Figure 14-15. Note that NewField2 is missing from the second group.

Message box from executing exaCurrentDb2( ) when refreshing with dbTwo
Figure 14-15. Message box from executing exaCurrentDb2( ) when refreshing with dbTwo

Note also that even before the two objects dbOne and dbTwo have been closed, the Access graphical interface has been updated to reflect the two new fields. In fact, if you uncomment the Stop line in Example 14-14 and check the design of the BOOKS table though Access, you will find that both new fields appear, even before the Refresh method is called.

 

All of this experimenting leaves us with a feeling that there are some mysteries associated with CurrentDb that Microsoft is not revealing (at least not readily). We can summarize as follows:

  • Invoking CurrentDb creates another member of the Databases collection.

  • On the other hand, each variable set through CurrentDb seems to affect the same database.

  • Refreshing is required to keep objects created through multiple invocations of CurrentDb current, belying the purpose of CurrentDb to some extent.

  • On the other hand, the Access interface does not require refreshing—it reflects the latest operations performed using any of the invocations of CurrentDb.

These issues notwithstanding, it makes sense to follow Microsoft’s recommendation to use CurrentDb, since it does reflect the current state of the Access environment more accurately than DBEngine(0)(0). Just be advised that some circumspection (refreshing) is needed when creating more than one variable through CurrentDb.

Finally, if you do use CurrentDb, then you should use it according to Microsoft’s rules, found in the Access 7.0 readme file acreadme.txt (but missing from the Access 8.0 readme file acread80.wri). Its text is reproduced here. Note the use of the word “once.”

Using the CurrentDb Function to Return a Reference to the Current Database

When you write code that includes a reference to the current database, you should declare a variable of type Database and use the CurrentDb function once to assign to it a pointer to the current database. You should avoid using CurrentDb to return the current database in a statement that also returns a reference to another object, such as a Set statement. It was possible to do this in some beta versions of Microsoft Access, but in Microsoft Access for Windows 95, your code may not run properly. For example, to determine the number of Document objects in the Documents collection, you should write code such as that shown in the following two examples:

Dim dbs As Database, con As Container
Set dbs = CurrentDb
Set con = dbs.Containers!Forms
Debug.Print con.Documents.Count

-or-

Debug.Print _
CurrentDb.Containers!Forms.Documents.Count

Code such as the following will not work:

Dim con As Container
Set con = CurrentDb.Containers!Forms
Debug.Print con.Documents.Count
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.133.206