So, I've been asked to update an old Classic ASP website. It did not use parameterized queries and there was very little input validation. To simplify things I wrote a helper function that opens a connection to the database, sets up a command object with any parameters, and creates a disconnected recordset [I think!?! :)] Here's the code:
Function GetDiscRS(DatabaseName, SqlCommandText, ParameterArray)
'Declare our variables
Dim discConn
Dim discCmd
Dim discRs
'Build connection string
Dim dbConnStr : dbConnStr = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=" & rootDbPath & "\" & DatabaseName & ".mdb;" & _
"Persist Security Info=False"
'Open a connection
Set discConn = Server.CreateObject("ADODB.Connection")
discConn.Open(dbConnStr)
'Create a command
Set discCmd = Server.CreateObject("ADODB.Command")
With discCmd
Set .ActiveConnection = discConn
.CommandType = adCmdText
.CommandText = SqlCommandText
'Attach parameters to the command
If IsArray(ParameterArray) Then
Dim cnt : cnt = 0
For Each sqlParam in ParameterArray
discCmd.Parameters(cnt).Value = sqlParam
cnt = cnt + 1
Next
End If
End With
'Create the Recordset object
Set discRs = Server.CreateObject("ADODB.Recordset")
With discRs
.CursorLocation = adUseClient ' Client cursor for disconnected set
.LockType = adLockBatchOptimistic
.CursorType = adOpenForwardOnly
.Open discCmd
Set .ActiveConnection = Nothing ' Disconnect!
End With
'Return the Recordset
Set GetDiscRS = discRS
'Cleanup
discConn.Close()
Set discConn = Nothing
discRS.Close() ' <=== Issue!!!
Set discRs = Nothing
Set discCmd = Nothing
End Function
My problem is that if I call discRS.Close() at the end of the function, then the recordset that is returned is not populated. This made me wonder if the recordset is indeed disconnected or not. If I comment that line out everything works properly. I also did some Response.Write() within the function using discRS values before and after setting ActiveConnection = Nothing and it properly returned the recordset values. So it seems to be isolated to discRS.Close().
I found an old article on 4guysfromrolla.com and it issues the recordset Close() in the function. I've seen the same thing on other sites. I'm not sure if that was a mistake, or if something has changed?
Note: I'm using IIS Express built into Visual Studio Express 2013
Disconnected recordset as far as I know refers to a recordset populated manually, not from database, e.g.used as multi dimensional array or kind of hash table.
So what you have is not a disconnected recordset since it's being populated from database, and by disposing its connection you just cause your code to not work properly.
Since you already have Set discConn = Nothing in the code you don't have to set it to nothing via the recordset or command objects, it's the same connection object.
To sum this all up, you should indeed get rid of tho following lines in your code:
Set .ActiveConnection = Nothing ' Disconnect!
discRS.Close() ' <=== Issue!!!
Set discRs = Nothing
Then to prevent memory leaks or database lock issues, you should close and dispose the recordset after actually using it in the code using the function e.g.
Dim oRS
Set oRS = GetDiscRS("mydatabase", "SELECT * FROM MyTable", Array())
Do Until oRS.EOF
'process current row...
oRS.MoveNext
Loop
oRS.Close ' <=== Close
Set oRS = Nothing ' <=== Dispose
To avoid all this hassle you can have the function return "real" disconnected recordset by copying all the data into newly created recordset. If relevant let me know and I'll come with some code.
In your function, you cannot close and clean up your recordset if you want it to be returned to the calling process.
You can clean up any connections and command objects, but in order for your recordset to be returned back populated, you simply do not close it or dispose of it.
Your code should end like this:
'Cleanup
discConn.Close()
Set discConn = Nothing
'discRS.Close()
'Set discRs = Nothing
'Set discCmd = Nothing
end function
In your code i can see:
Set .ActiveConnection = Nothing ' Disconnect!
So, this Recordset isn't already closed?
He is indeed using a disconnected recordset. I started using them in VB6. You set the connection = Nothing and you basically have a collection class with all the handy methods of a recordset (i.e. sort, find, filter, etc....). Plus, you only hold the connection for the time it takes to fetch the records, so back when Microsoft licensed their servers by the connection, this was a nice way to minimize how many userm were connected at any one time.
The recordset is completely functional, it's just not connected to the data source. You can reconnect it and then apply any changes that were made to it.
It was a long time ago, it seems that functionality has been removed.
You should use the CursorLocation = adUseClient. Then you can disconnect the recordset. I have created a function to add the parameters to command dictionary objects, and then return a disconnected recordset.
Function CmdToGetDisconnectedRS(strSQL, dictParamTypes, dictParamValues)
'Declare our variables
Dim objConn
Dim objRS
Dim Query, Command
Dim ParamTypesDictionary, ParamValuesDictionary
'Open a connection
Set objConn = Server.CreateObject("ADODB.Connection")
Set objRS = Server.CreateObject("ADODB.Recordset")
Set Command = Server.CreateObject("ADODB.Command")
Set ParamTypesDictionary = Server.CreateObject("Scripting.Dictionary")
Set ParamValuesDictionary = Server.CreateObject("Scripting.Dictionary")
Set ParamTypesDictionary = dictParamTypes
Set ParamValuesDictionary = dictParamValues
Query = strSQL
objConn.ConnectionString = strConn
objConn.Open
With Command
.CommandText = Query
.CommandType = adCmdText
.CommandTimeout = 15
Dim okey
For Each okey in ParamValuesDictionary.Keys
.Parameters.Append .CreateParameter(CStr(okey), ParamTypesDictionary.Item(okey) ,adParamInput,50,ParamValuesDictionary.Item(okey))
Next
.ActiveConnection = objConn
End With
objRS.CursorLocation = adUseClient
objRS.Open Command , ,adOpenStatic, adLockBatchOptimistic
'Disconnect the Recordset
Set objRS.ActiveConnection = Nothing
'Return the Recordset
Set CmdToGetDisconnectedRS = objRS
'Clean up...
objConn.Close
Set objConn = Nothing
Set objRS = Nothing
Set ParamTypesDictionary =Nothing
Set ParamValuesDictionary =Nothing
Set Command = Nothing
End Function
Related
I have the following, but it is not putting the count of records from the query in the MsgBox. Only the RR and the TT.
On Error Resume Next
Dim recordCount2
Set con = CreateObject("ADODB.Connection")
Set objRecordset = CreateObject("ADODB.Recordset")
con.ConnectionString = "Provider=SQLOLEDB;Integrated Security=SSPI;Persist Security Info=False;Data Source=servername\logon;Initial Catalog=database_name"
con.Open
strQry = "SELECT * FROM smd..table_name (nolock) WHERE CAST(LastRunDate AS DATE) = CAST(GETDATE() AS DATE) AND TableNameKey in ('value1', 'value2')"
Set data = con.Open(strQry)
objRecordset.Open strQuery, adoConnection, adOpenDynamic, adLockOptimistic
recordCount2 = objRecordset.Count
MsgBox "TT " & recordCount2 & "RR"
objRS.Close: Set objRS = Nothing
con.Close: Set con = Nothing
My guess is there's an error that's occurring which is being hidden by On error resume next, and recordCount2 = objRecordset.Count is not actually succeeding. Why do you have On error resume next anyway? Delete that line or comment it out, and your problem should become obvious.
Using On Error Resume Next isn't a "magic bullet". Especially like this quote from #ansgar-wiechers
"Contrary to popular belief it doesn't magically make errors go away."
On Error Resume Next is very useful but needs to be used in the correct context. While it is active any statement that raises an error is handled silently, the statement that raised the error is skipped and the inbuilt Err object is populated with the error details for error trapping.
As others have suggested the first thing you should do when debugging these types of problems is comment out On Error Resume Next then the issues I'm about to highlight you might have found yourself.
In the example above there are a couple of lines that are likely raising errors and being skipped, these are;
Set data = con.Open(strQry)
This statement appears to want to execute the query in strQry but con.Open() is the wrong method for this, the ADODB.Connection is already open it doesn't need opening again. You likely meant (but this is a pure guess);
Set data = con.Execute(strQry)
You don't appear to use data after you try running it so I would in this situation just comment it out for now.
The next is;
objRecordset.Open strQuery, adoConnection, adOpenDynamic, adLockOptimistic
which tries to open the ADO.Recordset using strQuery which doesn't appear to defined and neither is adoConnection you likely meant (again guess work);
objRecordset.Open strQry, con, adOpenDynamic, adLockOptimistic
If this statement raises an error and is skipped the statement
recordCount2 = objRecordset.Count
will itself error because the objRecordset .State will be set to adStateClosed.
After these suggestions you should have something like;
'On Error Resume Next
Dim recordCount2, constr
Set con = CreateObject("ADODB.Connection")
Set objRecordset = CreateObject("ADODB.Recordset")
constr = "Provider=SQLOLEDB;Integrated Security=SSPI;Persist Security Info=False;Data Source=servername\logon;Initial Catalog=database_name"
con.Open constr
strQry = "SELECT * FROM smd..table_name (nolock) WHERE CAST(LastRunDate AS DATE) = CAST(GETDATE() AS DATE) AND TableNameKey in ('value1', 'value2')"
'Set data = con.Open(strQry)
objRecordset.Open strQry, con, adOpenDynamic, adLockOptimistic
recordCount2 = objRecordset.Count
MsgBox "TT " & recordCount2 & "RR"
objRecordset.Close: Set objRecordset = Nothing
con.Close: Set con = Nothing
Avoid writing code as much as possible by writing only those lines/statements you are absolutely sure of. If you haven't seen a bit of documentation that explains why the statement x is neccessary to solve your problem, leave x off.
Before writing code, read the docs: RecordCount property:
... The cursor type of the Recordset object affects whether the number
of records can be determined. The RecordCount property will return -1
for a forward-only cursor; the actual count for a static or keyset
cursor; and either -1 or the actual count for a dynamic cursor,
depending on the data source. ...
So your plan is:
Open a connection
Get a recordset
Access its .RecordCount
The general skeleton for a VBScript contains:
Option Explicit
It does not contain the evil global OERN.
The database work specific skeleton contains:
Dim oCn : Set oCN = CreateObject("ADODB.Connection")
oCn.Open "DSN=???"
...
oCn.Close
It does not contain distracting Set x = Nothing tails.
For production scripts or test code wrt connection problems, you need to write long/complicated/tailored connection strings; for experimental code wrt small specific database features/problems/surprises a (once-for-all-puzzled-together-with-GUI-support) ODBC/DSN connection is more efficient and less error-prone.
The Recordset should be .Opened to play with the Cursor Type:
oRs.Open "SELECT AddressId FROM Person.Address", oCn, adOpenDynamic
Optional Locking left off (until you have documentary or experimental evidence that Lock Type influences .RecordCount).
If you try to run
Option Explicit
Dim oCn : Set oCN = CreateObject("ADODB.Connection")
oCn.Open "DSN=AdvWork"
Dim oRs : Set oRS = CreateObject("ADODB.Recordset")
oRs.Open "SELECT AddressId FROM Person.Address", oCn, adOpenDynamic
WScript.Echo ".RecordCount:", oRs.RecordCount
oCn.Close
you will be told: Variable is undefined: 'adOpenDynamic'. A bit of further (re)reading and your code will look like
Option Explicit
Const adOpenKeyset = 1
Dim oCn : Set oCN = CreateObject("ADODB.Connection")
oCn.Open "DSN=AdvWork"
Dim oRs : Set oRS = CreateObject("ADODB.Recordset")
oRs.Open "SELECT AddressId FROM Person.Address", oCn, adOpenKeyset
WScript.Echo ".RecordCount:", oRs.RecordCount
oCn.Close
output:
cscript 39519953.vbs
.RecordCount: 19614
and the world is a (little bit) better place.
#Brad Larson and #Ekkehard.Horner.... thanks for the guidance, i too was struggling to get the record count from a recordset using VBS and obtaining some info from the windows SYSTEMINDEX (it kept returning -1).
Setting up the Const adOpenKeyset = 1 and then adding that term to the [recordset.oen "SELECT....", oCn, adOpenKeyset] line made it work!!
you will be told: Variable is undefined: 'adOpenDynamic'. A bit of
further (re)reading and your code will look like
Option Explicit
Const adOpenKeyset = 1
Dim oCn : Set oCN = CreateObject("ADODB.Connection")
oCn.Open "DSN=AdvWork"
Dim oRs : Set oRS = CreateObject("ADODB.Recordset")
oRs.Open "SELECT AddressId FROM Person.Address", oCn, adOpenKeyset
WScript.Echo ".RecordCount:", oRs.RecordCount
oCn.Close
output: cscript 39519953.vbs .RecordCount: 19614 and the world is a
(little bit) better place.
When i check the result set. record count it returns -1 and while checking the recordset.EOF it returns true, thus the result set does not contain any value.
Dim con As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim strSQL
Private Sub cmd_login_Click()
Dim pass As String
con.ConnectionString = "Provider=msdaora;Data Source=localhost;User Id=ams;Password=krishnan;"
con.Open
strSQL = "Select passwrd from ams.login_details where username = 'Admin'"
rs.Open strSQL, con
If Not (rs.EOF) Then
If rs("passwrd") = txt_pass.Text Then
MsgBox rs("passwrd")
End If
End If
rs.Close
con.Close
End Sub
I forget to commit the statements in Oracle Sql Developer that's why not data was fetched from the database, When i executed the commit statement, it's working fine.
My question today is a rather simple one. What I have is a VB Module that contains the code to return me an ADODB.RecordSet object with records fetched from a SQL Query that has been executed. It works like this:
sSql_SerCheck = "SELECT DISTINCT Serial FROM dbo.WipReservedSerial WHERE Serial LIKE '" & serialTempSearch
sSql_SerCheck = sSql_SerCheck & "' ORDER BY Serial DESC "
dbGetRecordSet(sSql_SerCheck)
Then the results sit in object rs that is accessed like the following
temp = rs(0) 'For the value at the first column for the first record
rs.MoveNext 'This moves to the next record in the record set
Now what I am trying to do here is to the number of records contained within this recordset object. Now I did some research on the class and found there is a
RecordCount att.
So what I want to do is simple:
if( rs.RecordCount > 0) then
serCheck1 = rs(0)
MsgBox serCheck1
end if
The problem is my RecordCount returns -1. I found this article http://www.w3schools.com/asp/prop_rs_recordcount.asp that states that record count will return -1 for the following:
Note: This property will return -1 for a forward-only cursor; the actual count for a static or keyset cursor; and -1 or the actual count for a dynamic cursor.
Note: The Recordset object must be open when calling this property. If this property is not supported it will return -1.
How do I get this object to return the correct number of records??
The code for the VB Module is added below:
Public cn, rs
'Specify pSQL as SQL Statement
Function dbGetRecordset(sSql)
dbCloseConnection()
Set cn = CreateObject("ADODB.Connection")
cn.CommandTimeout = 600
cn.Open(Conn & SystemVariables.CodeObject.CompanyDatabaseName)
Set rs = CreateObject("ADODB.Recordset")
rs.Open sSql, cn, 3, 3
End Function
As your rs.RecordCount > 0 just checks whether the recordset is not empty, you can avoid .Recordcount (and all it's problems) by testing for Not rs.EOF
Don't trust secondary sources; the MS docs contain "... and either -1 or the actual count for a dynamic cursor, depending on the data source". So maybe your provider is to blame. In that case (or when you really need a specific number), a SELECT COUNT() would be a workaround
Don't use magic numbers as in rs.Open sSql, cn, 3, 3, but define (and doublecheck) your Consts like adOpenStatic, adLockOptimistic, ...
From Help
Set oRs = New ADODB.Recordset
oRs.CursorLocation = adUseClient
oRs.Open sSQL, sConn, adOpenStatic, adLockBatchOptimistic, adCmdText
Help has a full description of Cursors in What is a Cursor (ADODB Programmers Guide - Windows Software Development Kit).
You'll burn resources either locally or on the server to get a record count. If you are going through the data anyway, just count them.
This is how to go through a recordset one at a time.
Do While not .EOF
Outp.writeline .Fields("Txt").Value
.MoveNext
Loop
Set connection = CreateObject("adodb.connection")
connection.open "Driver=your driver details"
If connection.State = 1 Then
Set myRecord = CreateObject("ADODB.recordset")
sql= "select * from...."
myRecord.Open sql, connection
i = 0
Do While Not myRecord.EOF
i=i+1
myRecord.MoveNext
loop
msgbox "RecordCount="&i
myRecord.Close
END IF
set myRecord = nothing
set Connection = nothing
I was finding that the vbscript version of this was flaky... the best option seemed to use a CursorLocation of 3.. vbscript didn't seem to understand adUseClient.
set conn=CreateObject("ADOdb.connection")
conn.CursorLocation = 3
conn.open DSNQ
set rs = conn.execute("select * from sometable order by 1 desc")
msgbox (rs.RecordCount & " records" )```
I have a function that returns an ADO recordset, this in turn is used to feed subsequent functions. The first time it's passed to one of these functions, it runs fine; but in the next function, the recordset is at EOF. I assumed that my recordset was being passed By Ref, so tried using MoveFirst: this gives me an error because the cursor type is defaulted as forward only.
I tried the following:
Set it the parameter as By Val; this had no effect.
Created a copy of the recordset and used the original in the first function and the copy in the second; still the same problem (second is saying EOF, which implies that it's empty rather than at EOF; I can't imagine that the two recordsets are connected in some way, but perhaps).
Changed my DB code to use a different cursor type (adOpenStatic): This would actually be more correct for my application, but it gave very weird results. Namely, in a query that worked fine before, after the change, the first field was not being returned! This is kind of important, so I couldn't continue down this route.
Any ideas?
Main DB query function: GetData. Takes SQL and list of varchar parameters (if any) and returns recordset. It references dbOpen, which simply opens the connection with the supplied credentials.
Private Function GetData(ODBC As DBConnection, ByVal QuerySQL As String, Optional Parameters As Collection) As ADODB.Recordset
Dim DB As ADODB.Connection
Dim Query As ADODB.Command
Dim Parameter As ADODB.Parameter
Set DB = New ADODB.Connection
If dbOpen(DB, ODBC) Then
Set Query = New ADODB.Command
Query.ActiveConnection = DB
Query.CommandText = QuerySQL
If Not Parameters Is Nothing Then
For Each param In Parameters
Set Parameter = Query.CreateParameter(, adVarChar, adParamInput, Len(param), param)
Query.Parameters.Append Parameter
Next
Set Parameter = Nothing
End If
Set GetData = Query.Execute
' Uncommenting this seems to reset the data in GetData...
' A bit of a memory leak, but we assume VBA will take care of it :P
'dbClose DB
Else
MsgBox "Cannot connect to the database.", vbExclamation
Set GetData = Nothing
End If
End Function
I then do something like this:
Dim myRecords as ADODB.Recordset
' For the sake of argument, we assume this returns a nontrivial recordset
Set myRecords = GetData(someDSN, someSQL, someParameters)
Debug.Print myRecords.EOF ' Returns False
ID = writeHeader(myRecords)
Debug.Print myRecords.EOF ' Returns True
writeData ID, myRecords ' Breaks because at EOF
The writeHeader function will go through each record in the recordset, using MoveNext. However, if I pass myRecords by value or as a copy, then we still get the EOF problem. If I add MoveFirst at the end of the record iteration code in writeHeader, it will complain that I can't move to the beginning of such a recordset. If I change my GetData function so that Set GetData = Query.Execute is changed to:
Set GetData = New ADODB.Recordset
GetData.CursorType = adOpenStatic
GetData.Open Query
Then, when a query in my code is run -- which hasn't changed -- the first field is not a part of the recordset (!?) Say, for example, myRecords!ID is the first field: if this is referenced, I'll get a warning that said item isn't a member of the records (and it's not an enumerated member, either; so myRecords.Fields(1) just returns the next field in the query).
With a useful tip from #TimWilliams, I solved this by changing my GetData function to the following:
Private Function GetData(ODBC As DBConnection, ByVal QuerySQL As String, Optional Parameters As Collection) As ADODB.Recordset
Dim DB As ADODB.Connection
Dim Query As ADODB.Command
Dim Parameter As ADODB.Parameter
Dim Output As ADODB.Recordset
Set DB = New ADODB.Connection
If dbOpen(DB, ODBC) Then
Set Query = New ADODB.Command
Query.ActiveConnection = DB
Query.CommandText = QuerySQL
If Not Parameters Is Nothing Then
For Each param In Parameters
Set Parameter = Query.CreateParameter(, adVarChar, adParamInput, Len(param), param)
Query.Parameters.Append Parameter
Next
Set Parameter = Nothing
End If
Set Output = New ADODB.Recordset
Output.CursorType = adOpenStatic
Output.CursorLocation = adUseClient
Output.Open Query
Else
MsgBox "Cannot connect to the database.", vbExclamation
Set Output = Nothing
End If
Set GetData = Output
End Function
Then, before cursoring through the recordset, I use MoveFirst.
I have been working an ADO VBScript that needs to accept parameters and incorporate those parameters in the Query string that gets passed the the database. I keep getting errors when the Record Set Object attempts to open. If I pass a query without parameters, the recordset opens and I can work with the data. When I run the script through a debugger, the command object does not show a value for the parameter object. It seems to me that I am missing something that associates the Command object and Parameter object, but I do not know what. Here is a bit of the VBScript Code:
...
'Open Text file to collect SQL query string'
Set fso = CreateObject("Scripting.FileSystemObject")
fileName = "C:\SQLFUN\Limits_ADO.sql"
Set tso = fso.OpenTextFile(fileName, FORREADING)
SQL = tso.ReadAll
'Create ADO instance'
connString = "DRIVER={SQL Server};SERVER=myserver;UID=MyName;PWD=notapassword; Database=favoriteDB"
Set connection = CreateObject("ADODB.Connection")
Set cmd = CreateObject("ADODB.Command")
connection.Open connString
cmd.ActiveConnection = connection
cmd.CommandText = SQL
cmd.CommandType = adCmdText
Set paramTotals = cmd.CreateParameter
With paramTotals
.value = "tot%"
.Name = "Param1"
End With
'The error occurs on the next line'
Set recordset = cmd.Execute
If recordset.EOF then
WScript.Echo "No Data Returned"
Else
Do Until recordset.EOF
WScript.Echo recordset.Fields.Item(0) ' & vbTab & recordset.Fields.Item(1)
recordset.MoveNext
Loop
End If
The SQL string that I use is fairly standard except I want to pass a parameter to it. It is something like this:
SELECT column1
FROM table1
WHERE column1 IS LIKE ?
I understand that ADO should replace the "?" with the parameter value I assign in the script. The problem I am seeing is that the Parameter object shows the correct value, but the command object's parameter field is null according to my debugger.
I know this is old, but for anyone still fiding this (like I did via google):
If you're using Stored Procedures:
set cmd = Server.CreateObject("ADODB.Command")
with cmd
.ActiveConnection = db_connection
.CommandText = "stored_procedure_name"
.CommandType = adCmdStoredProc
.Parameters.Append .CreateParameter("#Parameter1",adInteger,adParamInput,,1)
.Parameters.Append .CreateParameter("#Parameter2",adVarChar,adParamInput,100,"Up to 100 chars")
.Parameters.Append .CreateParameter("#Parameter3",adBoolean,adParamInput,,true)
.Parameters.Append .CreateParameter("#Parameter4",adDBTimeStamp,adParamInput,,now())
end with
set rs = cmd.execute
'do stuff with returned results from select or leave blank if insert/delete/etc stored procedure
set rs = nothing
set cmd = nothing
If not, I beleive you change the .CommandText to your SQL statement with questions marks in place and your Parameters must follow the same order.
See http://www.devguru.com/technologies/ado/quickref/command_createparameter.html
For a breakdown of what values you're passing with CreateParameter, as well as a list of types and their descriptions.
After you create the parameter, you have to append it to the Command object's Parameters collection before you execute the Command:
Set paramTotals = cmd.CreateParameter
With paramTotals
.Value = "tot%"
.Name = "Param1"
End With
cmd.Parameters.Append paramTotals
You may also need to specify the Type and Size properties for the Parameter. Generally, I use the arguments of the CreateParameter function to set all the necessary properties in one line:
Set paramTotals = cmd.CreateParameter("Param1", adVarChar, adParamInput, 30, "tot%")
cmd.Parameters.Append paramTotals
I never got CreateParameter doing what I wanted. Proper parameterization is a necessity to avoid SQL injection, but CreateParameter is a complete PITA. Thankfully, there's an alternative: Command.Execute takes in parameters directly.
dim cmd, rs, rows_affected
set cmd = Server.createObject("adodb.command")
cmd.commandText = "select from Foo where id=?"
set cmd.activeConnection = someConnection
set rs = cmd.execute(rows_affected, Array(42))
It's much nicer when wrapped up in a proper abstraction. I wrote my own database class wrapping ADODB.Connection so I wouldn't have to do all of this manually. It relies a bit on other custom classes, but the gist of it should be apparent:
class DatabaseClass
' A database abstraction class with a more convenient interface than
' ADODB.Connection. Provides several simple methods to safely query a
' database without the risk of SQL injection or the half-dozen lines of
' boilerplate otherwise necessary to avoid it.
'
' Example:
'
' dim db, record, record_set, rows_affected
' set db = Database("/path/to/db")
' set record = db.get_record("select * from T where id=?;", Array(42))
' set record_set = db.get_records("select * from T;", empty)
' rows_affected = db.execute("delete from T where foo=? and bar=?",
' Array("foo; select from T where bar=", true))
private connection_
' An ADODB connection object. Should never be null.
private sub CLASS_TERMINATE
connection_.close
end sub
public function init (path)
' Initializes a new database with an ADODB connection to the database at
' the specified path. Path must be a relative server path to an Access
' database. Returns me.
set connection_ = Server.createObject ("adodb.connection")
connection_.provider = "Microsoft.Jet.OLEDB.4.0"
connection_.open Server.mapPath(path)
set init = me
end function
public function get_record (query, args)
' Fetches the first record returned from the supplied query wrapped in a
' HeavyRecord, or nothing if there are no results.
dim data: set data = native_recordset(query, args)
if data.eof then
set get_record = nothing
else
set get_record = (new HeavyRecordClass).init(data)
end if
end function
public function get_records (query, args)
' Fetches all records returned from the supplied query wrapped in a
' RecordSet (different from the ADODB recordset; implemented below).
set get_records = (new RecordSetClass).init(native_recordset(query, args))
end function
public function execute (query, args)
' Executes the supplied query and returns the number of rows affected.
dim rows_affected
build_command(query).execute rows_affected, args
execute = rows_affected
end function
private function build_command (query)
' Helper method to build an ADODB command from the supplied query.
set build_command = Server.createObject("adodb.command")
build_command.commandText = query
set build_command.activeConnection = connection_
end function
private function native_recordset (query, args)
' Helper method that takes a query string and array of arguments, queries
' the ADODB connection, and returns an ADODB recordset containing the
' result.
set native_recordset = build_command(query).execute( , args) ' Omits out-parameter for number of rows
end function
end class