Running Asynchronous Queries

Creating and running queries in an ODBCDirect workspace is similar to creating and running queries in a Microsoft Jet workspace. You create the query by invoking the CreateQueryDef method on a Connection object, and then use the Execute or OpenRecordset methods on the resulting query.

You can use asynchronous queries so that users can continue using your application while the query runs. You can also give users the ability to cancel asynchronous queries if they are taking too long. The following example runs an asynchronous query.

Function DeleteLargeSales() As Boolean
	Dim wrk As Workspace, rst As Recordset
	Dim cnn As Connection, qdf As QueryDef
	Dim strConnect As String, strSQL As String
	Dim errObj As Error

	On Error GoTo Err_DeleteLargeSales
	' Create ODBCDirect workspace.
	Set wrk = DBEngine.CreateWorkspace("ODBC", "Admin", "", dbUseODBC)
	' Create connection string.
	strConnect = "ODBC;DSN=Pubs;UID=SA;PWD=;DATABASE=Pubs"
	' Open connection on workspace.
	Set cnn = wrk.OpenConnection("", dbDriverNoPrompt, False, strConnect)

	' Create QueryDef.
	Set qdf = cnn.CreateQueryDef("DeleteLargeSales")
	strSQL = "DELETE FROM sales WHERE qty > 100"
	qdf.SQL = strSQL

	' Run query asynchronously.
	qdf.Execute dbRunAsync

	Do Until Not qdf.StillExecuting
	' Additional code runs here while query runs.
	' Check StillExecuting property to determine whether query has finished.
	Loop

	DeleteLargeSales = True

Exit_DeleteLargeSales:
	On Error Resume Next
	cnn.Close
	wrk.Close
	Exit Function

Err_DeleteLargeSales:
	For Each errObj In Errors
		Debug.Print errObj.Number, errObj.Description
	Next errObj
	DeleteLargeSales = False
	Resume Exit_DeleteLargeSales
End Function

The preceding example uses a QueryDef object on a Connection object to run an asynchronous query. You can also use the Execute method directly on the Connection object, as shown in the following example.

Dim wrk As Workspace, cnn As Connection
Dim strConnect As String

' Create ODBCDirect workspace.
Set wrk = DBEngine.CreateWorkspace("ODBC", "Admin", "", dbUseODBC)
strConnect = "ODBC;DSN=Pubs;UID=SA;PWD=;DATABASE=Pubs"
' Open connection.
Set cnn = wrk.OpenConnection("", dbDriverNoPrompt, False, strConnect)
' Execute SQL statement.
cnn.Execute "DELETE FROM sales WHERE qty > 100", dbRunAsync
cnn.Close

When you run a query asynchronously, you can use the StillExecuting property to determine if the query has completed. If the value of the StillExecuting property is True, the query has not yet completed. If you want to cancel an asynchronous query, use the Cancel method, as shown in the following example.

Function CancelAsynchQuery() As Boolean
	Dim wrk As Workspace, cnn As Connection, strConnect As String
	Dim errObj As Error

	On Error GoTo Err_CancelAsynchQuery
	Set wrk = DBEngine.CreateWorkspace("ODBCDirect", "Admin", "", dbUseODBC)
	strConnect = "ODBC;DSN=Pubs;UID=SA;PWD=;DATABASE=Pubs"
	Set cnn = wrk.OpenConnection("", dbDriverNoPrompt, False, strConnect)

	' Start transaction in order to roll back if needed.
	wrk.BeginTrans
	cnn.Execute "DELETE FROM sales WHERE qty > 100", dbRunAsync

	' Perform other operations.
		.
		.
		.

	' If query is still running, cancel and roll back.
	If cnn.StillExecuting Then
		cnn.Cancel
		wrk.Rollback
	' If query is complete, commit transaction.
	Else
		wrk.CommitTrans
	End If
	CancelAsynchQuery = True

Exit_CancelAsynchQuery:
	On Error Resume Next
	cnn.Close
	wrk.Close
	Exit Function

Err_CancelAsynchQuery:
	For Each errObj In Errors
		Debug.Print errObj.Number, errObj.Description
	Next errObj
	CancelAsynchQuery = False
	Resume Exit_CancelAsynchQuery
End Function

You can use the StillExecuting property and the Cancel method with QueryDef, Connection, and Recordset objects.

A Connection object can support only one asynchronous operation at a time. Also, you can’t perform another DAO operation, such as recordset manipulation, on a Connection object while an asynchronous query runs on the same Connection object. After an asynchronous query is complete, you can then begin running another asynchronous query on the same Connection object. You must first test the value of the StillExecuting property to determine whether you can start the next asynchronous operation. Depending on the capabilities of your servers, to run multiple asynchronous queries at the same time, you may need to create separate Connection objects and run each asynchronous query on its own Connection object.

When running an action query as an asynchronous query, you’ll generally want to run it as part of a transaction. Be aware, however, that if you call the CommitTrans method while the asynchronous query is still running, your code will pause at the CommitTrans method until the query finishes. For this reason, it is more efficient to periodically check the StillExecuting property and continue to perform other work while the query runs. Once the StillExecuting property returns False, you can then call the CommitTrans method. This prevents your code from pausing at the CommitTrans method.

Note If you cancel an action query that is not part of a transaction, the query updates records up to the point where you called the Cancel method. The operation will be partially complete and will not be rolled back. For this reason, you should use the Cancel method on an action query only within the scope of a transaction. Additionally, if you start an asynchronous query in a procedure and the procedure exits before the query has completed, the query will continue to run.

To improve performance when you’re retrieving data from an ODBC data source, you can cache records locally. A cache is a space in local memory that holds the data most recently retrieved from the server. If you’re performing repeated operations on a set of data, caching that data makes those operations faster because you don’t have to retrieve the data from the server each time you need it.

In ODBCDirect queries, use the CacheSize property of the QueryDef object to specify the number of records to cache. The default cache size is 100 records. The following example shows how to reset the cache size to 200 records.

Sub SetCacheSize()
	Dim wrk As Workspace, qdf As QueryDef, rst As Recordset
	Dim cnn As Connection, strConnect As String

	Set wrk = CreateWorkspace("ODBCDirect", "Admin", "", dbUseODBC)
	strConnect = "ODBC;DSN=Pubs;UID=SA;PWD=;DATABASE=Pubs"
	' Open connection.
	Set cnn = wrk.OpenConnection("", dbDriverNoPrompt, False, strConnect)
	' Create temporary QueryDef.
	Set qdf = cnn.CreateQueryDef("tempquery")
	With qdf
		.SQL = "SELECT * FROM roysched"
		.CacheSize = 40
		Set rst = .OpenRecordset()
	End With
	' Print recordset to Debug window.
	PrintRecordset rst
	rst.Close
	cnn.Close
End Sub