This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
ADO Command Strategies
William R. Vaughn
This article is excerpted from Chapter 5 of Bill Vaughn's new book from
Apress, ADO Examples and Best Practices, (ISBN 1-893115-16-x). I suspect
most of you know Bill. I think of him as something of a "Mr. VB-DATA"
thanks to his best-selling Hitchhiker's Guide to Visual Basic and SQL Server,
his extremely popular and content-dense sessions at VBits through the years, and
his untiring help to thousands of VB developers in venues such as the VBDAT
list.
[Chapter 5, "ADO Command Strategies" is 45 pages long. In addition
to providing a comprehensive discussion of the ADO Command object, it includes
nine tips, five notes, and two warnings. (If you've read Hitchhiker's Guide, you
know how useful—sometimes funny, sometimes almost irreverently candid—Bill's
tips can be.) After thinking about David Small's September feature,
"Reacquainting Yourself with the ADO Command Object," it occurred to
me that it would be useful to follow up with some material from Bill's chapter
on the ADO command object. What follows is just a selection of the sections I
found most useful. Oh, and by the way, be sure to look for Rob Macdonald's book,
also from Apress, Serious ADO: Universal Data Access with VB. Rob's another
frequent VBD contributor. Look for the first of a series of articles on VB.Net
beginning next month.—Ed.]
It's important for you to know that there sare many different ways to execute
Recordset queries—many of which don't require use of the Command object. The
one important case where the Command object is required is when you have to
capture parameters returned from stored procedures
When it comes time to
execute a SQL query, the best object to use is often the ADO Command. However,
as you'll see, it's not always the best choice. Fortunately, due to ADO's
flexibility, you have other alternatives to draw on. One thing you might not
know—it's "under-documented"—is that all ADO Command objects
appear as methods on their associated Connection objects. This innovative
technique (well, they stole it from RDO) permits you to code the Command by
name, followed by its parameters, followed by the Recordset to contain the
rowset. Cool.
Tip: When you create a Command object, it should be created once. That
is, create as many objects as necessary—don't create one and change its
properties (other than the parameters) to suit the immediate requirement. At one
time, there was evidence that ADO was making many round trips to the server to
"figure out" how to execute the query. My tests show this is no longer
happening in ADO 2.5—at least not always. The entire setup phase seems to be
done entirely on the client. But this is still overhead that you don't have to
tolerate more than once.
Setting ADO properties is really a matter of knowing what ADO expects. If
you're supposed to provide a number, make sure it's in the correct range.
The Name property
If you want to use the (cool) "Command as Connection
method" technique to execute your Command object, you must name it. It's
not a bad idea to do so in any case. If you're executing a stored procedure, the
name must match the name of the stored procedure. Otherwise, you're free to name
the command after jungle plants if you're so inclined. Just remember to set your
name early—before you set the ActiveConnection property. And don't forget to
use a String constant or variable to name your Command.
Tip: If you pass in an unquoted value [as the Name Property] instead of a
String constant or declared variable, Visual Basic assumes it's the name of a
Variant variable that will have some value later at runtime, unless you have
Option Explicit turned on (you should). In this case, you'll get a healthy
"Variable Not Defined" warning at compile time.
The (so-called) Prepared property
In theory, the Prepared property was designed to reduce work on the
server by pre-compiling ad hoc queries so subsequent executions would use a
temporary stored procedure instead of repeating the compile phase each time the
query is executed. However, this is not the case with ADO's
implementation—keep reading.
Since ODBC was invented some years ago, SQL Server has gotten much smarter—it
now knows how to leverage existing (in cache) compiled query plans. That is,
once you execute a query from ADO (or by any means), SQL Server constructs a
query plan, saves it in the procedure cache, and executes it. When the query is
done, SQL Server marks the query plan as "discardable" but leaves it
in memory as long as it can. When another identical (or close-enough) query
comes in, which is very likely in systems running multiple clients, SQL Server
simply re-uses the cached plan. This saves a significant amount of time and
greatly improves scalability. It makes SQL Server actually run faster as more
users are added, assuming they're doing about the same things with the same set
of queries.
ADO and its ODBC and OLE DB data providers know about this strategy, and in
most cases they execute sp_executesql to take advantage of this feature.
However, this puts the Prepared property in a quandary. It insists on creating
temporary stored procedures, but the data providers insist on using
sp_executesql. The result? Chaos. I describe what happens a little later when
executing Command objects is discussed.
My recommendation for the Prepared property: forget it—at least for SQL
Server. For other providers, set up a trace that shows exactly what's going on—what
the server is being asked to do.
The CommandTimeout property
The CommandTimeout property indicates how long to wait (in seconds)
while executing a command before terminating the attempt and generating an
error. Remember, CommandTimeout starts when the database server has accepted the
command and ends when the database server returns the first record. If the
server or network is busy (or not answering at all), this setting won't help you
to get control again.
In RDO (and in the ODBC API), I had another option that was lost when I
converted to ADO-retry on timeout. That is, if you wanted to keep waiting after
a CommandTimeout, you could simply pass a flag back to the event handler and
keep waiting. This is not implemented in ADO. Why? BHOM (another
"technical" term I learned in the Army that means, "beats the
hell out of me").
Note: ADO timeout settings are independent of network timeout. Since the
low-level network driver makes a synchronous network API call, and since this
call doesn't return until the network timeout expires, the ADO timeout code is
blocked.
Handling parameter-based queries
Most queries you execute require one or more parameters to govern the
rowset created. These parameters are usually applied to the WHERE clause of a
query, but they can be used in a variety of other ways. When you construct any
parameter-based query, you have to describe the query parameters one way
or another and supply the runtime values, but you don't have to use the Command
object—not unless you expect to deal with a stored procedure return status or
with output parameters. You can use other techniques to pass input parameters,
and I'll discuss those next. Basically, there are several approaches you can
take when constructing parameter queries:
- Construct a Command object in code, which exposes an empty ADO
Parameters collection. This approach can generate the Parameters collection
using the Refresh method or by constructing the Parameters one-by-one.
- Construct an SQL statement that includes the parameters in the query
string. This approach can construct an sp_executesql query instead of having
ADO construct one for you.
- The Visual Database Tools, including the Data Environment Designer, can
also construct parameter-based SQL statements and expose these as Data
Environment Designer-based Command objects. These are discussed more
completely in the Hitchhiker's Guide to Visual Basic and SQL Server. In
this material, I'll stay focused on ADO coding.
- Download Sprocaddin.exe.
How ADO Command objects manage your parameters
When you use the ADO Parameters collection to manage your parameters,
it's ADO's responsibility to get these parameters crammed into the query in the
right places and in the right format. ADO is also responsible for dealing with
"framing" quotes. That is, if the parameter has embedded single-quotes
to delineate strings (most do), ADO will automatically double these up
(replacing a single quote with two single quotes)
When working with Command
objects, it's your responsibility to describe the parameters correctly,
unless you use the Refresh method. This means constructing the Parameters
collection one parameter at a time in the order the data provider expects them.
No, ADO and its data providers don't support "named" parameters, so
you have to specify them in the right order. Knowing how to describe the
Parameter datatype, size, precision, scale, and shoe size is your
responsibility. If you get it wrong, you'll get an error. If you get them out of
order, who knows what will happen...
There are a number of techniques I can illuminate to make your
parameter-based queries more efficient and easier to construct. Eventually, all
of your production applications will evolve to depend on parameter queries to
both improve performance (both system and developer performance) and to simplify
the development of component-based designs.
Constructing the Parameters collection
The ADO Command object's Parameters collection manages all flavors of
parameters: gazintas (input), gazouta (output), and gazinta-gazouta
(input-output-bi-directional) parameters. Remember, input parameters can be
applied to ad hoc queries as well as stored procedures. The trick is learning
how and when to construct the Command object's Parameters collection in code. As
I have said before, there are two approaches:
- Use the Command.Parameters.Refresh method to get ADO and the associated
provider to construct the Parameters collection for you, based on the
CommandText you've provided.
- Construct the Command.Parameters collection
yourself-parameter-by-parameter, based on your understanding of how the
parameters are defined.
Each technique has its advantages and disadvantages in terms of developer and
application performance. IMHO, neither technique should be used from the
middle tier if you can help it. Why? Well, consider that the time taken to
execute the extra code to build the Command object and the appropriate Parameter
objects (one at a time) is wasted. It has to be re-executed each time the ASP is
referenced or the MTS component is executed. Yes, the Command objects make the
process far simpler to code. If you're looking for a simple solution with
somewhat limited scalability, then keep reading.
Using the Refresh method
The Command.Parameters.Refresh method technique seems to do all of
the work for you—it constructs the Command object's Parameters collection for
you in a single line of code. That's good and bad. It's good in the sense that
you don't have to worry about how ADO creates the Parameter objects (for the
most part, that is—it usually gets them right). It's bad in that ADO and the
provider take a round trip to the server to figure out how to do so, and this
can be costly (as we've already discussed). However, since this can be a
one-time performance hit early in the life of the application, it might not make
that much difference.
Remember to set the ActiveConnection property before attempting to use the
Refresh method—ADO needs a valid connection to hit the database to generate
the parameters.
Tip: Actually, you don't even have to use the Refresh method if you don't
want to, and using it might even cause ADO to execute an extra round trip. When
you try to read a property of an un-initialized Command.Parameters collection
for the first time, ADO constructs the Parameters collection for you—just as
if you'd executed the Refresh method.
The Parameter.Size property
Use the Size property to determine the maximum size for values
written to or read from the Value property of a Parameter object. If the size of
the parameter exceeds this upper limit, you'll trip a trappable error.
Constructing Parameter objects with CreateParameter
You can create individual Parameter objects and add them to the
Parameters collection using the Append method:
Dim Pr as Parameter
Set Pr = New Parameter
With Pr
.Name = "P1"
.Type = adVarChar
.Direction = adParmInput
.Size = 30
.Value = "Fred graduates in 2000"
End With
Cmd.Append Pr
If you charge by the line, this is the best approach, but it's much easier to
do all of this with a single line of code:
.Parameters.Append .CreateParameter("P1", adVarChar,_
adParamInput, 30, "Fred graduates in 2000")
The CreateParameter method constructs the entire Parameter object in a single
step. It doesn't include the NumericScale or Precision properties, but it does
include the other essential properties—including Value. Because this method
reminds you of all of the essential properties, prompting you for each one as
you type in the Visual Basic IDE, its use qualifies as a "best
practice."
Rolling your own Parameter queries
No, you don't have to use the Parameters collection to execute a
parameter query, or queries without parameters for that matter. For instance,
you can construct a query in a string and pass it to the Source property of a
Recordset or the CommandText property of a Command object. You can also execute
any stored procedure as a method of the Connection object. The stored procedure
parameters are simply passed as method arguments.
Passing arguments with Variant arrays
Another way to pass in input parameters is to use a Variant array.
This approach is kinda cool. And better yet, if you don't provide one of the
elements, ADO doesn't submit the parameter—it assumes the provider will insert
the "default" value. If there's no default value set, either in the
called stored procedure or in the initial Parameter collection, ADO returns a
trappable error: "-2147217900 Incorrect syntax near the keyword
'DEFAULT.'"
Link to http://www.betav.com
To find out more about Visual Basic Developer and Pinnacle Publishing, visit their website at
http://www.pinpub.com/
Note: This is not a Microsoft Corporation website. Microsoft is not responsible for its content.
This article is reproduced from the October 2000 issue of Visual Basic Developer. Copyright 2000, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. Visual Basic Developer is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-788-1900.