Classic ASP disconnected recordsets issue

2019-08-08 00:15发布

问题:

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

回答1:

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.



回答2:

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


回答3:

In your code i can see:

Set .ActiveConnection = Nothing   ' Disconnect!

So, this Recordset isn't already closed?



回答4:

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.



回答5:

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