Is this programmatically a bad idea? - database

Currently I'm trying to create some coding short cuts for our website. Right now to query a database and return a record set 2 functions have to be called:
GetDBConn returns an open ado connection object to the connection string passed in.
GetRS returns a record set object based on the ado connection and sql passed in.
set objConn = GetDBConn(connString, commandTimeout, connTimeout, cursorType)
set objRs = GetRS(objConn, sql)
I want to essentially write those two as a single function but my question really becomes this... I am pretty sure that you're supposed to close and destroy your ado connections when done with them. If I dim the connection inside the new function, query the database and return the record set, I can't close and destroy the connection inside the function or else the record set returned by the function becomes useless (i.e. that connection object is never explicitly closed/destroyed). Is that ok? Will it have any negative impact on the SQL/Web Servers?
Thanks

Not closing connections will cause problems with your SQL server's resources. Depending on your hardware and how many connections are getting established, you may not notice a problem. Or it may make the server inaccessible...

Using it for record sets is probably not a good idea (too easy to forget to close them), however opening / closing a connection shouldn't be a problem. I use the following two functions on all my legacy Classic ASP sites:
conn.asp:
<%
Dim oConn
Sub openConn()
Set oConn = Server.CreateObject("ADODB.Connection")
oConn.Open sConn 'connection string here'
End Sub
Sub closeConn()
If IsObject(oConn) Then
On Error Resume Next
If oConn.State = 1 Then
oConn.Close
End If
Set oConn = Nothing
Err.Clear
On Error Goto 0
End If
End Sub
%>
Then I include conn.asp and use openConn() once at the top of the page and closeConn() at the bottom.
The On Error Resume Next is usually regarded as a "bad coding" practice, however in this case I think it's appropriate otherwise you'll be getting errors on all the pages you use closeConn() on for those edge cases where oConn.State fails for any reason.

I've always used the dbhelper.asp provided by the MS reference app FmStocks
The functions always return a disconnected recordset so you never get in trouble
The functions are like this one:
Function RunSQLReturnRS(sqlstmt, params())
On Error Resume next
' Create the ADO objects'
Dim rs , cmd
Set rs = server.createobject("ADODB.Recordset")
Set cmd = server.createobject("ADODB.Command")
' Init the ADO objects & the stored proc parameters'
cmd.ActiveConnection = GetConnectionString()
cmd.CommandText = sqlstmt
cmd.CommandType = adCmdText
cmd.CommandTimeout = 900 ' 15 minutes
collectParams cmd, params
' Execute the query for readonly'
rs.CursorLocation = adUseClient
rs.Open cmd, , adOpenForwardOnly, adLockReadOnly
If err.number > 0 then
BuildErrorMessage()
exit function
end if
' Disconnect the recordset'
Set cmd.ActiveConnection = Nothing
Set cmd = Nothing
Set rs.ActiveConnection = Nothing
' Return the resultant recordset'
Set RunSQLReturnRS = rs
End Function

Related

Error: Row cannot be located for updating. Some values may have been changed since it was last read. On changing provider from SQLOLEDB to MSOLEDBSQL

On change of provider from SQLOLEDB to MSOLEDBSQL in the ADODB connection string, we get the error:
-2147217864 Row cannot be located for updating. Some values may have been changed since it was last read.
The connection string is:
Provider=MSOLEDBSQL;SERVER=servername;APP=Applicationname;DATABASE=databasename;WSID=id;Trusted_Connection=yes;MARS Connection=True;DataTypeCompatibility=80
And the code looks like:
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open("SELECT * FROM tableName WHERE 1 = 2", Adoconnection, adOpenStatic, adLockBatchOptimistic, CommandTypeEnum.adCmdText)
rs.AddNew
'Add the fields
...
...
rs.UpdateBatch ''this line throws error
Now, when in the connection string of provider is changed to SQLOLEDB, with the same code it works great without any issue.
Try adding a timestamp, or so called "rowversion" column to the table. (use type timestamp - which has ZERO to do with time).
Also, if you have any bit columns in that table, then make sure they are not null, and make sure a default value of (0) is set for that bit column.
And if the application has linked tables, then re-link your tables after you made the above change server side.
I found out the issue, it was in the SQL triggers.
The respective table had some update statements on the trigger. Adding SET NOCOUNT ON just before the update statement in the trigger helped me to avoid this error.
I found my way to this thread because I had a similar error. I am using Access 365 VBA 32-bit on Windows 10. Here's a snippet of my code (some details omitted):
Dim cmd As ADODB.Command
Set cmd = New ADODB.Command
cmd.ActiveConnection = GetCurrentConnection()
cmd.CommandText = sql
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open cmd, , adOpenDynamic, adLockOptimistic
With rs
.Fields("FailedImport") = True
.Fields("ImportErrors") = ReadErrorsFromResponse(xmlDoc)
.Update
End With
I also encountered the same error as in the OP. In my case, I didn't get the error until I included the update to the boolean field called FailedImport. I found my way to this forum post from 2008, containing this comment (formatting mine):
Had same error, however had it with recordsets rather than data control.
From another forum, I learnt that if you are using a static cursor for the recordset, using adLockBatchOptimistic instead of adLockOptimistic solved the problem.
So, I changed this line:
rs.Open cmd, , adOpenDynamic, adLockOptimistic
to this:
rs.Open cmd, , adOpenDynamic, adLockBatchOptimistic
And my code worked again! Hope this helps someone else in the future!

How to work with multiple recordsets out of one procedure from SQL Server in vba

I have linked a MS Access frontend to SQL Server (and somehow regret that decision). Now I have a bunch of procedures with a complex but similar WHERE part but different SELECT DISTINCT so I would like to combine them to a single procedure like
SELECT * FROM myTable1 INNER JOIN #tblFromFunc()
SELECT * FROM myTable2 INNER JOIN #tblFromFunc()
and a predefined table-valued function #tblFromFunc which will handle my WHERE just once. Hope for better performance and to make maintenance easier.
That works fine on the SQL Server an I even can get the first of this two independent results (just tried two of them) visible in MS Access by using DAO.QueryDef and DAO.Recordset in vba.
I found this description of .NextRecordset here, in short as vba:
DIM rst as DAO.Recordset
SET rst = functionConnectServer("NameOfSp")
booNext = True
intCount = 1
With rst
Do While booNext
Do While Not .EOF
Debug.Print , .Fields(0), .Fields(1)
.MoveNext
Loop
booNext = .NextRecordset
intCount = intCount + 1
Loop
End With
but if I used it in Acceess 2010, I got the response, that somehow .NextRecordsetis not supported anymore. So I cannot move to the second recordset, to be honest, I am even not sure, if that second recordset reaches my frontend.
Any hint would make me happy and I am even open to total different strategies for this problem.
While the DAO method requires specific objects to work with compound SQL statements, consider an ADO connection where you assign to NextRecordset:
' REFERENCE Microsoft ActiveX Data Objects 2.* Library
Set conn = New ADODB.Connection
Set rst = New ADODB.Recordset
conn.Open "Driver={SQL Server};Server=server;Database=db;Trusted_connection=yes;"
rst.Open "EXEC myStoredProc", conn
' FIRST RECORDSET
With rst
Do While Not .EOF
Debug.Print , .Fields(0), .Fields(1)
.MoveNext
Loop
End With
' SECOND RECORDSET
Set rst = rst.NextRecordset()
With rst
Do While Not .EOF
Debug.Print , .Fields(0), .Fields(1)
.MoveNext
Loop
End With
rst.Close()
conn.Close()
Set rst = Nothing
Set conn = Nothing
While you may not be able to loop through many resultsets, you can place loop in a defined function and call it after each Set:
Function RetrieveData(rs As Recordset)
With rs
Do While Not .EOF
Debug.Print , .Fields(0), .Fields(1)
.MoveNext
Loop
End With
End Function
Sub DatabaseProcess()
...
rst.Open "EXEC myStoredProc", conn
' FIRST RECORDSET
Call RetrieveData(rst)
' SECOND RECORDSET
Set rst = rst.NextRecordset()
Call RetrieveData(rst)
rst.Close(): conn.Close()
Set rst = Nothing: Set conn = Nothing
End Sub

ADODB open recordset fails / "Operation is not allowed when object is closed"

I have the following UDF in excel which uses ADO to connect to my MSSQL server. There it should execute the scalar udf "D100601RVDATABearingAllow".
For some reason the parameters that I try to append are not send to the sql server. At the server only:
SELECT dbo.D100601RVDATABearingAllow
arrives.
MY EXCEL UDF:
Function RVDATA(Fastener) As Long
Dim cnt As ADODB.Connection
Dim rst As ADODB.Recordset
Dim Cmd1 As ADODB.Command
Dim stSQL As String
Const stADO As String = "Provider=SQLOLEDB.1;Data ................"
'----------------------------------------------------------
Set cnt = New ADODB.Connection
With cnt
.ConnectionTimeout = 3
.CursorLocation = adUseClient
.Open stADO
.CommandTimeout = 3
End With
'----------------------------------------------------------
Set Cmd1 = New ADODB.Command
Cmd1.ActiveConnection = cnt
Cmd1.CommandText = "dbo.D100601RVDATABearingAllow"
Cmd1.CommandType = adCmdStoredProc
'----------------------------------------------------------
Set Param1 = Cmd1.CreateParameter("Fastener", adInteger, adParamInput, 5)
Param1.Value = Fastener
Cmd1.Parameters.Append Param1
Set Param1 = Nothing
'----------------------------------------------------------
Set rst = Cmd1.Execute()
RVDATA = rst.Fields(0).Value
'----------------------------------------------------------
rst.Close
cnt.Close
Set rst = Nothing
Set cnt = Nothing
'----------------------------------------------------------
End Function
When I use adCmdStoredProc the whole thing fails and in the vba debugger the properties of the recordset has a lot of "Operation is not allowed when object is closed" (may sound a bit different, the message is translated)
When I don't use adCmdStoredProc I get the message that the variable Fastener was not provided.
I think that maybe something is wrong in the way I open the recordset.
In other treads I read about using the "SET NOCOUNT ON" option, but that did not work either.
Does anyone have a idea?
Regards Lumpi
Ran into this error as well (in my case I am using a Stored Procedure to retrieve some information). I had made some changes which caused the execution to malfunction.
The error disappeared when I put SET NOCOUNT ON as the first statement of the Stored Procedure.
You do not need to SELECT the server side function, just provide its name ("[tra-CAE400-1].dbo.D100601RVDATABearingAllow") in the .CommandText property.
Also you should set the .CommandType property to "stored-procedure" (property reference on w3schools.com).
Then adodb will know that you are talking about calling a function, and not trying to send a plain sql-command.
Chances are that it will then allow you to define the parameters on the command object.
But the parameters you define on the command object should correspond exactly (in name and type) to the ones that are defined as the arguments of the function in the sql server.
An example from microsoft.com on using the command-object with a stored procedure
ADO Reference on microsoft.com
Another possible cause of this is debug statements. I just spent far too long trying to work out why this wouldn't work for me, the Proc on the database worked fine, the data it was supposed to insert was inserted, the VBA code worked fine, but there was nothing in the recordset.
Final solution was to go through the procs that had been built and remove the PRINT statements.
To test if this is the problem, run your proc on SQL Server manually, then look at the messages tab of the results, if there's anything there other than "Command(s) completed successfully." you need to eliminate those messages. "SET NOCOUNT ON" will get rid of the row count messages, but there may be others.
I'm assuming that after 5 years the OP has solved this particular problem, so this is just for anyone like me that finds this while searching for the same problem.
I also ran into this with a stored procedure. Did you SET NOCOUNT = OFF; at the bottom of your code? That is what worked for me after lots of googling. Also, if you have any other code that runs, you have to wrap it in Nocount = on/off, INCLUDING insert and update statements. You would think that an insert statement wouldn't matter but wrapping the code that way is what kept me from committing suicide today.
In our shop we often use lines like this in our stored procedures to assist with debugging:
RAISERROR('Debug message here',0,1) WITH NOWAIT;
This also breaks opening a recordset in Excel vba. I believe the complete answer for this question is, in the stored procedure:
use SET ROWCOUNT OFF
remove all PRINT statements
remove all RAISEERROR statements used for debugging (ie severity of 0)

Connecting to a MS Access database through a function

This is my function:
Public Function DBConnection(ByVal path As String)
' This function makes the database connection and returns the object
' to reference it.
cn = New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" + path + ";")
cn.Open()
Return cn
End Function
As you can see, I want to initialize a database connection and return it so I can use it in my forms. This function is in a module and my variables are as follows:
Public cn As OleDbConnection
Public cmd As OleDbCommand
Public dr As OleDbDataReader
But I'm not sure how I can use this in my forms, do I just call the function DBConnection and then proceed with my SQL statements? Or do I have to do something else? Help would be very much appreciated, cheers.
Also, I need some opinions. My application relies on a MS Access database. Is it better to initialize the connection on Form_Load and then close the connection when the user closes the program, or open and close the connections as the queries are run? I'm planning to use some database queries on multiple forms hence the reason I was putting it into a module, but I'm not 100% on how I should proceed with this.
Thanks.
From: How to bind Microsoft Access forms to ADO recordsets
Private Sub Form_Open(Cancel As Integer)
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
'Use the ADO connection that Access uses
Set cn = CurrentProject.AccessConnection
'Create an instance of the ADO Recordset class, and
'set its properties
Set rs = New ADODB.Recordset
With rs
Set .ActiveConnection = cn
.Source = "SELECT * FROM Customers"
.LockType = adLockOptimistic
.CursorType = adOpenKeyset
.Open
End With
'Set the form's Recordset property to the ADO recordset
Set Me.Recordset = rs
Set rs = Nothing
Set cn = Nothing
End Sub
A couple things. That function will open a connection every time it's called. You better make sure you are closing the database as well, or that will start eating up memory.
You might look at some other options (such as using NHibernate or another ORM.)
However, if you stay with this model, you could then use the cn that is returned to access your database.

Requested operation requires an OLE DB Session object... - Connecting Excel to SQL server via ADO

I'm attempting to take Excel 2003 and connect it to SQL Server 2000 to run a few dynamicly generated SQL Queries which ultimately filling certain cells.
I'm attempting to do this via VBA via ADO (I've tried 2.8 to 2.0) but I'm getting an error while setting the ActiveConnection variable which is inside the ADODB.Connection object. I need to resolve this pretty quick...
Requested operation requires an OLE DB Session object, which is not supported by the current provider.
I'm honestly not sure what this error means and right now I don't care. How can get this connection to succeed so that I can run my queries?
Here is my VB code:
Dim SQL As String, RetValue As String
SQL = " select top 1 DateTimeValue from SrcTable where x='value' " 'Not the real SQL
RetValue = ""
Dim RS As ADODB.Recordset
Dim Con As New ADODB.Connection
Dim Cmd As New ADODB.Command
Con.ConnectionString = "Provider=sqloledb;DRIVER=SQL Server;Data Source=Server\Instance;Initial Catalog=MyDB_DC;User Id=<UserName>;Password=<Password>;"
Con.CommandTimeout = (60 * 30)
Set Cmd.ActiveConnection = Con ''Error occurs here.
' I'm not sure if the rest is right. I've just coded it. Can't get past the line above.
Cmd.CommandText = SQL
Cmd.CommandType = adCmdText
Con.Open
Set RS = Cmd.Execute()
If Not RS.EOF Then
RetValue = RS(0).Value
Debug.Print "RetValue is: " & RetValue
End If
Con.Close
I imagine something is wrong with the connection string but I've tried over a dozen variations. Now I'm just shooting in the dark....
Note/Update: To make matters more confusing, if I Google for the error quote above, I get a lot of hits back but nothing seems relevant or I'm not sure what information is relevant....
I've got the VBA code in "Sheet1" under "Microsoft Excel Objects." I've done this before but usually put things in a module. Could this make a difference?
You have not opened your connection yet. I think you need a Con.Open before you assign it to the Command object.
Con.ConnectionString = "Provider=sqloledb;DRIVER=SQL Server;Data Source=Server\Instance;Initial Catalog=MyDB_DC;User Id=<UserName>;Password=<Password>;"
Con.CommandTimeout = (60 * 30)
Con.Open
Set Cmd.ActiveConnection = Con 'Error occurs here.
Cmd.CommandText = SQL
Cmd.CommandType = adCmdText

Resources