SQL Clone
SQLServerCentral is supported by Redgate
Log in  ::  Register  ::  Not logged in

Introduction to ADO Part 4 - Combining It All

By Andy Warren,

In three previous articles (Introduction to the ADO Connection Object, Introduction to ADO Part 2 - Recordsets, Introduction to ADO - The Command Object) I've been trying to give you a simple easy to follow introduction to ADO. Why would a DBA need to know ADO? Well, it may come as a shock, but not all data access code is done in stored procedures! So reason number one is that if it's going to be running against one of your servers, it helps if you can follow the code when you see a performance issue or bottleneck. The second reason is that once you get comfortable with it, you may find it to be the tool of choice for building those small utilities we all have, generating reports, etc.

For this week I promised some code that would combine the connection, command, and recordset objects into one piece of code and you'll find that below. I tried to show you different ways of solving some fairly common problems. Keep in mind that ADO has a lot of options - there are lots of ways to solve problems and not all of them are shown here!

As I started writing the code sample, I kept thinking about things I didn't cover in the first three articles. Tons of things! But in keeping with my original goal, most of those things we can cover later. I did think of two that I thought were worth discussing at this point; client side filtering and disconnected recordsets, so I included some code to show them at work.

Client side filtering just means that once you do your select of x records into your recordset, you can apply additional an additional filter to the recordset without having to requery the database. The only catch is that is has to be a simple filter in the form fieldname=fieldvalue. You can apply multiple filters to come close to the power of using 'and' in your filter. If you need an 'or', a new recordset is probably your best bet. One point to remember about filtering is to always check to see that you have records in the recordset after filtering. If no records matched, the recordset would be empty. Why use this? No round trip! This lets the developer create a rich user experience without the overhead of another call to the server. That also means the server will scale higher.

Disconnected recordsets are used exactly like a regular recordset. The only difference is, once the recordset is populated, the activeconnection property is set to nothing. Ideally at that point the client application has NO connection to the database. The user can add, edit, delete records from the recordset...without affecting the server. Once all changes are complete, instead of calling the update method, the updatebatch method is called, sending all the changes over in one big...batch! This is definitely optimistic locking, so there is the possibility that conflicts may happen (the record was changed by another user sometime between the select and the updatebatch). ADO gives you the tools to handle those fairly easily, but we'll save that discussion for another day. For now, just take a look at how it works and think about doing more work on the client with fewer trips to the server combine to make both a better application and a more scaleable one.

Finally, here is the code.

Sub Demo(Optional ServerName As String = ".")

'set a reference to ADO 2.6 in VB

Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim cmd As ADODB.Command
Dim SQL As String
Dim params As ADODB.Parameters
Dim param As ADODB.Parameter

'we'll use this later
Const OrderID As Long = 10259

'first thing is to open a connection. For this demo, we're using a 'dsn-less' 'connection 'string. If you look at the servername parameter being passed in to 'this sub, you'll see it defaults to '.' - an easy way to say connect to the local 'server - and I'm using that variable in the connection string to make the code 'more dynamic. I'm also using the VB App.Title property to make the application 'name dynamic. For this demo we'll always be using Northwind so I've hard coded 'that and set it to use a trusted connection.
Set cn = New ADODB.Connection
cn.CursorLocation = adUseClient
cn.ConnectionTimeout = 10
cn.CommandTimeout = 10
cn.Open "Provider = SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=Northwind;Data Source=" & ServerName & ";Application Name=" & App.Title

'lets start by creating a stored proc that we'll reuse in a moment. This one just 'returns counts of the titles in the customers table. First thing is to drop it if
'it already exists. Assigning your TSQL to a string variable makes it easier to 'debug - you can just debug.print it to the immediate window and copy/paste into 'query analyzer to test.
SQL = "if exists(select * from sysobjects where name='usp_gettitlecounts') drop proc usp_getTitlecounts"
cn.Execute SQL, adExecuteNoRecords

'now to create, Im building the TSQL string to create the proc - in this example 'Im adding cr/lf pairs to make the code more readable when you view it later using 'QA. Note that for both this execute and the previous one Im using the const 'adExecuteNoRecords' to tell SQL that I dont need anything returned - saves 'network traffic.
SQL = "create proc usp_GetTitleCounts as " & vbCrLf
SQL = SQL & "--test proc for use in ADO demo app" & vbCrLf
SQL = SQL & "set nocount on" & vbCrLf
SQL = SQL & "select contacttitle, count(*) as titlecount from customers group by contacttitle order by contacttitle"
cn.Execute SQL, , adExecuteNoRecords

'now lets execute the proc and return a recordset. Because the results will be 'read only anyway (its a group by), we want to use the fastest recordset - which 'is what we get by default. Since the proc has no parameters, it's hardly worth 'using a command object, we'll just continue to use our connection object.
SQL = "usp_getTitleCounts"
Set rs = cn.Execute(SQL)

'now lets loop through and see what it returned
Do Until rs.EOF
Debug.Print rs.Fields("ContactTitle"), rs.Fields("TitleCount")

'we're done, so clean up the recordset
Set rs = Nothing

'now lets switch back to using stored procs, this time using one that already 'exists and uses a parameter AND a return value - now we need a command object. 'We'll reuse 'our existing connection object (set activeconnection).
Set cmd = New ADODB.Command
With cmd
Set .ActiveConnection = cn
.CommandText = "CustOrdersDetail"
.CommandType = adCmdStoredProc
Set params = .Parameters
End With

'this proc has one parameter, plus the return value. Whether you need the return value
'depends on how the proc was coded.
params.Append cmd.CreateParameter("@RETURN_VALUE", adInteger, adParamReturnValue, 0)
params.Append cmd.CreateParameter("@OrderID", adInteger, adParamInput, 0)

'we're going to get the details for order # 10259 - so we assign that to the input
'parameter, then execute the proc - which returns a recordset, in this case
'we wind up with a 'read only' recordset because of the way we are returning it,
'not because the recordset couldnt be updated (its only a select).
params("@OrderID") = OrderID
Set rs = cmd.Execute

'since you already know how to iterate, we'll just close this one and move on!
Set rs = Nothing
Set cmd = Nothing

'lets take a look at some richer functionality of the recordset. We're going to do 'basically what the stored proc did - return a bunch of detail records, but then 'we will disconnect the recordset, modify a couple, then reconnect and do the 'update. In this demo Im not killing the main connection (cn), but in practice you 'would. Set rs = New ADODB.Recordset
rs.Open "Select ProductID, UnitPrice from [Order Details] where orderid=" & OrderID, cn, adOpenStatic, adLockBatchOptimistic

Set rs.ActiveConnection = Nothing

'now I happen to know that this particular query will return two records, lets 'find the one we want to update by leveraging the client side filtering ability of 'the recordset. Then 'we'll just add one to the unit price. Note the use of the 'check for BOF - if the record is missing (or your filter is bad) and you try to 'update - you'll get an error!
rs.Filter = "ProductID=37"
If Not rs.BOF And Not rs.EOF Then
rs.Fields("UnitPrice") = rs.Fields("UnitPrice") + 1
End If

'now lets reconnect and make the update
Set rs.ActiveConnection = cn

'clean up
Set rs = Nothing

'and we're done with our connection, so close it too
Set cn = Nothing

End Sub

As always, I enjoy hearing from readers - questions, comments, complaints - just post in the attached discussion forum!

Total article views: 9954 | Views in the last 30 days: 2
Related Articles

Help filtering user connection with sp_who

Help filtering user connection with sp_who


n.value = isNull(@Value,n.Value) Different Record Counts Returned.

Different recordsets counts returned


More on Returning a Subset of a Recordset

Last week's tip created quite a discussion on different techniques for returning a subset of a recor...


Returning a Subset of a Recordset

Stateless programming can be tricky... In case you ever need to return a specified subset of a query...


VB6.0 and SQL 2005 database ado/recordset not supported

error adding record using Addnew/Update via recordset

performance tuning    
stored procedures