Visual Basic, with its powerful drawing tools and easy programming methodologies, is an excellent tool for creating front-end data-access programs. The Jet engine enables Visual Basic users to create powerful database applications. In the world of corporate computing with large networks and multiple users, client/server database applications are the required solution.
When creating database applications with the Jet engine, the Database Engine is a part of the application. The database files can reside on a shared file server, but each application that uses the database contains the Database Engine code. Such an application is called remote database application. Many users can share the database, but the methodology is actually that of sharing files. To enable users to share a database, all the applications that use the database must have access to the database files. Also, none of the applications can open the database in “exclusive” mode. When you open a database in exclusive mode, you prevent other applications from using the database.
In this chapter, you learn about the following:
In client/server applications, the Database Engine and the database files reside on a machine separate from the applications that access the database files. Therefore, the Database Engine can run on a powerful server platform while the client applications run on less powerful machines. The server can even be running a non-Windows operating system. Figure 9.1 depicts the differences between remote and client/server database applications.
An architectural diagram of the client/server application environment.
A client/server application accesses data by having the client application send a query to the server. The client application usually sends the query through a networking (interprocess communications, or IPC) protocol. The protocol that the application uses is determined by the protocol stacks in the computing environment. Some common protocol stacks include NetBEUI, NetBIOS, TCP/IP, and IPX/SPX. Even though the query is being made across the computer network, the database server blocks the application calling it until the server retrieves the data and sends the data back to the client application.
Open Database Connectivity (ODBC) is a method of communication to client/server databases in Visual Basic. ODBC is part of Microsoft’s Windows Open Systems Architecture (WOSA), which provides a series of application program interfaces (APIs) to simplify and provide standards for various programming activities. The goal is to have all applications communicating through the same set of APIs. ODBC is just one piece of the WOSA picture. Other components include telephone services (TAPI), messaging services (MAPI), and open data services (ODS).
This chapter covers the basics and the advanced issues in developing client/server applications.
Although ODBC is intended for client/server applications, ODBC drivers are available for remote databases like Access and dBASE. In this chapter, these drivers are used to develop the sample application and code samples.
Before you can access ODBC databases, you must configure the ODBC data source names, the ODBC drivers, and the configuration values used in ODBC.INI. You also should understand the structure of an ODBC driver and the ODBC API as well as some ODBC-related terminology.
ODBC drivers are classified as either single-tier or multiple-tier:
Each ODBC driver conforms to one of three levels of capabilities: Core Level, Level 1, and Level 2.
The Core Level is the base set of capabilities that an ODBC driver must contain. All ODBC drivers must meet the requirements of this level. The Core Level capabilities for an ODBC driver are the following:
Level 1 includes the capabilities of Core Level ODC drivers plus data-source connectivity through driver-specific dialogs. All ODBC drivers that the Jet engine uses must meet or exceed this level.
Level 2 includes the capabilities of a Level 1 ODBC driver plus the following capabilities:
Before accessing an ODBC database, you must install on your system the appropriate ODBC driver for that database. You install this driver by using the ODBC Manager applet in the Windows Control Panel. You can also use the ODBCADM.EXE program supplied with Visual Basic.
To install an ODBC driver, just perform the following steps:
Opening the ODBC Manager application to maintain ODBC drivers and data sources.
Browsing the available data sources with the ODBC Manager.
Using the ODBC Manager to add a new ODBC driver to the system.
Completing the ODBC driver installation.
The ODBC driver that you install must be at least Level 1 compliant. Most ODBC drivers meet this requirement. The driver’s manufacturer can tell you whether its has all the Level 1 capabilities.
An ODBC data source is a named connection to a database. ODBC.INI stores the data-source entry, which consists of the information necessary for connecting to the database server. This information includes the database name and location, the ODBC driver to use, and various attributes specific to the ODBC driver that you are using.
To create an ODBC data source, two methods are available:
Adding an ODBC data source is easy. To add a data source through the ODBC Data Manager control applet, perform the following steps:
Choosing an ODBC driver to use when adding a data source.
Setting the ODBC driver-specific options for a data source.
To add a data-source name using the RegisterDatabase method in DBEngine, you first must examine the method to understand the information that it requires:
DBEngine.RegisterDatabase dbName , driver , silent, attributes
The parameters in this syntax are the following:
Parameter | Definition |
dbName | A user-definable string expression that specifies the data source’s name (for example, MyDatabase). |
driver | A string expression that indicates the installed driver’s name (for example, ORACLE) as listed in ODBCINST.INI. Note that this expression is the name of the driver section in ODBCINST.INI, not the driver’s DLL name. |
silent | True specifies that the next parameter (attributes) indicates all connection information. False specifies to display the Driver Setup dialog box and ignore the contents of the attributes parameter. |
attributes | All connection information for using the ODBC driver. This parameter is ignored if silent is set to False. |
Listing 9.1 is a code example that demonstrates how to add a data source programmatically for the Address Book example in Chapter 8.
Listing 9.1 Adding a data source programmatically for the Address Book Example
Because you use ODBC primarily for accessing client/server databases, delays are possible in database logins and queries, particularly when the database server resides on a mainframe or host computer connected by a modem or bridge. The delay is primarily due to the time necessary to issue calls to the database server through the local area network (LAN) or dial-up communication lines.
Visual Basic maintains a default database-login time of 20 seconds. This value is used when the user issues an OpenDatabase command. If Visual Basid cannot establish the connection within this time frame, the OpenDatabase call fails. You can alter this value through DBEngine’s LoginTimeout property, as in the following statement:
The default time-out value for database queries is 60 seconds. You can alter this value by changing the database QueryTimeout property or a QueryDef’s ODBCTimeout property. Be careful when performing queries on tables that contain BLOB (Binary Large Objects) information. Queries on such tables are typically very slow, so you might want to increase the time-out value according to the BLOB data’s size and the database server’s speed. If you specify 0, no time-out occurs. The following examples demonstrate alterations to the default time-out:
The ODBC section of VB.INI or APP.INI also maintains these time-out values, as you will see in the upcoming sections.
As mentioned earlier, ODBC operation depends on two files that the ODBC Data Manager creates: ODBC.INI and ODBCINST.INI.
ODBCINST.INI is located in the Windows directory and contains information about the ODBC drivers installed on the system. Listing 9.2 shows a sample ODBCINST.INI file.
Listing 9.2 A Sample ODBCINST.INI File
At the top of the ODBCINST.INI file is the [ODBC Drivers] section. This section lists all installed ODBC drivers. The Installed Drivers dialog box of the ODBC Data Manager lists all installed ODBC drivers. Note that for client/server databases or multiple-tier drivers, the entries are simple. Each section contains a “Driver” and “Setup” entry. Single-tier drivers have additional parameters, such as the SQLLevel and APILevel that the driver supports.
The ODBC.INI file maintains a list of all defined ODBC data sources. The file contains all the information about the data source as specified through the Add Data Source dialog box (see fig. 9.6) or by the attributes parameter of the RegisterDatabase method. Listing 9.3 shows a sample ODBC.INI file.
Listing 9.3 A Sample ODBC.INI File
The last set of parameters that affect ODBC operation is contained in an ODBC section of VB.INI or your application’s initialization file, APP.INI. Table 9.1 indicates the valid entries, their purpose, and any default values.
Table 9.1 Application-Specific Parameters That Affect ODBC Operation
Entry | Purpose | Valid Values |
TraceSQLMode | Trace the ODBC API calls that the jet engine sends | 0 = Don’t Trace (default) 1 = Trace |
QueryTimeout | Abort queries that don't finish within the specified number of seconds | 60 seconds (default) |
LoginTimeout | Abort login attempts that don't finish within the specified number of seconds | 20 seconds (default) |
ConnectionTimeout | Close active connections that are idle for the specified number of seconds | 600 seconds (default) |
AsyncRetryInterval | Set the interval for asking the server whether the query is finished; specified in milliseconds | 500 milliseconds (default) |
AttachCaseSensitive | Use case sensitivity when attaching to tables | 0 = No case sensitivity (default) 1 = Use case sensitivity |
SnapshotOnly | Create both dynaset and snapshot recordset objects or only snapshots | 0 = Create both (default) 1 = Create only snapshots |
AttachableObjects | List (in a string) the database server object types to which you can connect | “TABLE,VIEW,SYSTEM TABLE,ALIAS,SYNONYM" (default) |
When applications read in external data types, a one-to-one correspondence between types does not always occur. Such is the case when reading external ODBC data sources. You need to understand how ODBC data types relate to Jet engine data types. Such an understanding is important when you attach an ODBC table to a database. The Jet engine maps ODBC data types to Jet data types. Table 9.2 describes these relationships.
Table 9.2 Comparing ODBC and Jet Data Types
ODBC Data Type | Description | Visual Basic Data Type |
SQL_BIT | Single-bit binary data | YES/NO |
SQL_TINYINT | A whole number between 0 and 255 inclusive | Integer |
SQL_SMALLINT | A whole number between 32,767 and –32,768, inclusive | Integer |
SQL_INTEGER | A whole number between 2,147,483,647 and –2,147,483,648, inclusive | Long |
SQL_REAL | A floating-point number with seven-digit precision | Single |
SQL_FLOAT, SQL_DOUBLE |
A floating-point number with 15-digit precision | Double |
SQL_TIMESTAMP, SQL_DATE, SQL_TIME |
Date and time data | DateTime |
SQL_CHAR | Character string | If 255 characters or more, Text; if less than 255 characters, Memo |
SQL_VARCHAR | A variable-length character string with a maximum length of 255 | Text |
SQL_BINARY | Fixed-length binary data | If 255 characters or more, Binary, where the precision is Field Size; if less than 255 characters, OLE Field |
SQL_VARBINARY | Variable-length binary data with a maximum length of 255 characters | Binary |
SQL_LONGVARBINARY | Variable-length binary data with a source-dependent maximum length | OLE Field |
SQL_LONGVARCHAR | A variable-length character string with a source-dependent maximum length | Memo |
SQL_DECIMAL, SQL_NUMERIC |
Signed, exact, numeric value with precision and scale | If the scale is 0, then ?; if the precision is 4 or greater, Integer; if the precision is 9 or greater, Long; if the precision is 15 or greater, Double; if the scale is less than 0 and the precision is greater than 15, Double |
When accessing ODBC databases, you can use two methods: directly through the ODBC API or with the Jet Data Access Object. Note that for Jet to access the ODBC databases, the Data Access Object calls the ODBC API internally.
The ODBC API consists of approximately 30 functions. It supports field-by-field data retrieval and uses the SQL syntax for manipulating and defining data. The various ODBC drivers conform to one of the three levels of capabilities (Core Level, Level 1, and Level 2). To use the ODBC API directly, you must know the level of ODBC that the driver supports.
The Jet Data Access Object can use ODBC drivers that provide Level 1 support. Therefore, Jet does not support the complete set of ODBC functions. Table 9.3 lists all the ODBC functions that the Jet engine supports.
Table 9.3 ODBC API Functions Supported by the Jet Engine
Function | Purpose |
SQLAllocConnect | Obtains a connection handle. |
SQLAllocEnv | Obtains an environment handle. You use one environment handle for one or more connections. |
SQLAllocStmt | Allocates a statement handle. |
SQLCancel | Cancels a SQL statement. |
SQLColumns | Returns the list of column names in specified tables. |
SQLDescribeCol | Describes a column in the result set. |
SQLDisconnect | Closes the connection. |
SQLDriverConnect | Connects to a specific driver by a connection string or requests that the Driver Manager and the driver display connection dialog boxes for the user. |
SQLError | Returns additional error or status information. |
SQLExecDirect | Executes a statement. |
SQLExecute | Executes a prepared statement. |
SQLFetch | Returns a result row. |
SQLFreeConnect | Releases the connection handle. |
SQLFreeEnv | Releases the environment handle. |
SQLFreeStmt | Ends statement processing, closes the associated cursor, discards pending results, and, optionally, frees all resources associated with the statement handle. |
SQLGetData | Returns part or all of one column of one row of a result set. |
SQLGetInfo | Returns information about a specific driver and data source. |
SQLGetTypeInfo | Returns information about supported data types. |
SQLNumResultCols | Returns the number of columns in the result set. |
SQLParamData | Used with SQLPutData, supplies parameter data at execution time. |
SQLPrepare | Prepares a SQL statement for later execution. |
SQLPutData | Sends part or all of a data value for a parameter. |
SQLRowCount | Returns the number of rows affected by an insert, update, or delete request. |
SQLSetConnectOption | Sets a connection option. |
SQLSetParam | Binds a buffer to a parameter in a SQL statement. Replaced with SQLBindParam. |
SQLSetStmtOption | Sets a statement option. |
SQLSpecialColumns | Returns information about the optimal set of columns that uniquely identifies a row in a specified table, or the columns that are automatically updated when a transaction updates any value in the row. |
SQLStatistics | Returns statistics about a single table and the list of indexes associated with the table. |
SQLTables | Returns the list of table names stored in a specific data source. |
SQLTransact | Commits or rolls back a transaction. |
Visual Basic does not include the ODBC constants and functions that Visual Basic developers need. However, this exclusion does not prohibit you from using the ODBC API. All the ODBC functions are in the file ODBC.DLL, which Visual Basic includes. The documentation for these functions, as well as the Visual Basic prototypes, are available in the ODBC Developer’s SDK.
Using the ODBC API directly has some advantages. The API is quite flexible because it is a very low-level API. In other words, the ODBC API is used for directly issuing commands to a database server. The developer must build all data structures for retrieving data. This gives the programmer maximum power in designing applications. Also, the API is fast, goes directly to the database server, and imposes no additional overhead. Finally, ODBC is designed to provide connectivity to all databases and thus is portable across languages and databases.
On the other hand, the ODBC also presents some disadvantages. First, you need an external development kit, the ODBC SDK, to get the prototypes and documentation required for using the API functions. Because the ODBC is a very low-level API, you find yourself building many “wrapper” or helper functions to provide a higher level of interface for your application. The API provides no object model on which you can build and use. Additionally, the ODBC API is subject to change, leaving the developer with the possibility of developing to a moving target. And finally, only rarely will an application be likely to need to access the ODBC API directly.
Visual Basic’s Data Access Object is the preferred method for accessing ODBC databases because it uses the ODBC API internally, provides a higher-level interface, and is based on an object model.
The Data Access Object consists of two sets of functions: the Data Definition Language (DDL) and the Data Manipulation Language (DML). Table 9.4 lists the interfaces that comprise the Data Access Object and their purpose.
Table 9.4 Data Access Object Interfaces
Data Access Object Interface | Purpose | Type |
---|---|---|
DBEngine | A top-level object that corresponds to the Jet engine | DML, DDL |
Workspace | A container for open databases that supports simultaneous transactions | DML, DDL |
Database | Represents the database layout; corresponds to a native Jet database, an external database, or an ODBC connection | DML, DDL |
TableDef | Represents a physical database table definition | DDL |
QueryDef | Defines a stored query or precompiled SQL statement; stored in the database rather than the code | DDL |
Recordset | Returns the results of a query into a database | DML |
Field | Represents a column of data in a table | DDL |
Index | Represents a stored index for a table | DDL |
Parameter | Represents a stored query parameter associated with a parameterized query | DDL |
User | Defines and enforces database security | DDL |
Group | A collection of users with similar privileges | DDL |
Relation | Defines relationships among fields in two or more tables | DDL |
Property | Represents a stored property associated with an object | DDL |
Container | Enumerates all objects stored in a database | DDL |
Document | Objects of a common type that share a container | DDL |
When using the Data Access Object, your Visual Basic program can access ODBC databases in the same manner as desktop databases. The preferred method for accessing ODBC databases is to attach the tables in an ODBC data source to your application’s database. Listing 9.4 shows how you attach tables to an ODBC database.
Listing 9.4 Attaching Tables to an ODBC Database
In listing 9.4, the string passed into the CreateTableDef method is the TableDef name. The Connect string consists of the following items:
Attaching a table as just described is one method for accessing an ODBC database with the Data Access Object. You also can use the Data Access Object by directly opening and using DBEngine’s OpenDatabase method and specifying an ODBC connection string. You shouldn’t use this access method, however, because it slows your application’s performance.
When attaching to an ODBC database, the Jet engine stores a great deal of information about the table locally, including table and field information as well as server capabilities. When an application opens an ODBC database directly, Jet asks the server for this information every time that the application performs a query.
To access ODBC data from Visual Basic, you can use one of two methods: by writing directly to the ODBC API or by using the Data Access Object.
When developing client/server applications with ODBC, you must consider a different set of performance concerns than when working with remote databases.
Visual Basic 4.0 replaces Visual Basic 3.0’s dynaset, snapshot, and Table objects with one generic object, Recordset.
When creating a Recordset object, you specify the type of recordset that you are creating. The type can be dynaset, snapshot, or Table. When accessing remote data through an ODBC connection, you can create two types of Recordset objects: dynaset or snapshot.
A Recordset object of type dynaset or snapshot returns as the result of a data query. The dynaset type contains a “live,” updatable view of the data in the underlying tables. When the dynaset changes, the underlying tables are immediately updated; conversely, when the tables change, the dynaset is updated. A snapshot is a static or nonupdatable view of the data in the underlying tables.
To understand more clearly when to choose one type rather than another, you need to know how each type of recordset is populated. When you create a snapshot, Visual Basic retrieves all the data in the selected columns of the matching rows and places the retrieved data in the recordset. Conversely, when you create a dynaset object, Visual Basic retrieves only the primary key (or bookmark) of the query. For both recordsets, Visual Basic stores in memory the results of the query.
The Jet engine is optimized to return only as many records as it needs to fill the resulting display screen. The rest of the data is retrieved either in idle time or through user scrolling (scrolling indicates an on-demand situation where the records are retrieved only when the user moves to records not visible on the screen). Again, the snapshot recordset retrieves all the selected columns in the matching rows, but dynaset retrieves the matching primary keys.
When you need rows of information, the snapshot recordset has all the information in memory and readily accessible for use. Because a dynaset recordset contains only the primary keys in memory, the Jet engine sends a separate query to the server to request all the selected columns. Jet optimizes this process by requesting clusters of information rather than one row at a time. The dynaset retrieves about 100 records surrounding the current record to create the impression that data is being retrieved rapidly.
Because they use different methods to retrieve and cache data, the performance of snapshot and dynaset recordsets differs greatly. The following are performance considerations for each type:
When accessing data, your application can use two methods: queries based on the Jet engine and pass-through queries. In a Jet engine query, the engine compiles the statement and then sends it to the server. In a pass-through query, your application sends directly to the database server the SQL statement that you enter.
Pass-through queries offer a few advantages over compiled queries:
Using SQL pass-through statements also has some disadvantages:
A stored procedure is analogous to a Jet engine QueryDef. Such a procedure consists of a set of SQL statements that the server stores. The application program accesses a stored procedure to retrieve and update data. In some environments, stored procedures perform all data requests and updates because the application programs have no direct access to the remote tables.
If your application must update data in an environment in which you have no direct access to remote tables, you must execute a SQL pass-through that calls a stored procedure.
Client/server computing offers network users great power and flexibility. A powerful database server can reside on a fast CPU to provide information to a host of clients. As this chapter has shown, Visual Basic offers a variety of methods for accessing ODBC database servers. You can use the ODBC APIs for directly accessing the database, and use the Jet engine’s Data Access Objects to provide a higher-level object interface. You can combine this interface with SQL pass-through statements to access accessing a server’s functionality directly. Visual Basic showcases these capabilities in providing a quality database-development environment.
To learn more about the Jet engine, see Chapter 8, "Accessing Other Databases with the Jet Engine". This chapter provides more information about the Jet engine and demonstrates how to use it to bring external data into database applications.
© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.