VB6 ADODB subsequent SELECT queries timeout after moving from SQL2000 to SQL2008 - sql-server

I'm facing timeout issues when performing multiple SELECTs after moving database to a different server, in a relic (VB6 application) I've been tasked to patch up. Things worked flawlessly in the old environemnt, the new one contains carbon copies of the old tables.
NOTE! The new database was built from scratch (that is, DBA ran many CREATE TABLE + INSERT scripts to create carbon copies of tables then fill them with the old data).
This is the error source:
// "conn" is being initialized outside the function
Public Function PerformOperation(ByRef conn as ADODB.Connection, query as string) as Boolean
Dim rs as ADODB.Recordset
//This below is the timeout source
rs.Open conn, query, adOpenStatic, adLockReadOnly
If Not (rs.EOF or rs.BOF) Then
rs.MoveFirst
//assign data to many variables
End If
ExitPoint:
If Not (rs Is Nothing) Then
If (rs.State = adStateOpen) Then rs.Close
Set rs = Nothing
End If
Exit Function
Error:
MsgBox "Blah blah"
Resume ExitPoint
Resume 0
End Function
Function is called like this
conn = New ADODB.Connection
conn.ConnectionString = "..."
conn.Open
For i = 1 To RowCount //reading data from a grid component (data is correct!)
//very long select here... kept short
query = "SELECT something FROM somewhere WHERE <manyFields> = <manyValues>"
If PerformOperation(conn, query) = True Then
//UPDATE another table based on the SELECT data
// NOTE: this occurs on a DIFFERENT, INDEPENDENT ADODB.Connection object
End If
Next i
The first time PerformOperation is called it goes through fine, second time through it times out no matter how long i set the CommandTimeout. It also works OK if the cycle "cycles" a single time.
Big problem here is, all I have to try things out is the production environment so I'll need to be extra careful. I also don't have a very deep DBA knowledge at hand... Just a very very old piece of software which will eventually be ported to .NET but needs to be dealt with in the meantime...
How can I check and/or fix this issue ? This has to work for any length of the For cycle
Many thanks for any suggestion (as always, if i missed any essential detail point it out and i'll provide it if i can).
EDIT #1
I've expanded the first and secondo code blocks to provide deeper details about what's going on. Comments changed in order to try and fix highlighting (single quotes mess up coloring).
EDIT #2
Enabling Multiple Active Result Sets (MARS) in the connection string didn't help either.

I'm a bit suprised that it has worked so well so far.
You have to close the recordset that you open, otherwise you will use up one database connection for each query you run. The database will reclaim the unused connections after a while, but if you use up too many connections too fast you will reach the limit for the number of connections per user, and the database will refuse any more connections.
The reason that it worked at all with the older database is probably because it created new connections as needed. This has been changed in later versions, and you would have to change the settings to allow the older, more resource wasteful behaviour. Changing that setting is however not a good solution to the problem.
Close the recordset and remove the reference when you don't need it any more. That will free up the connection so that you can use it for another query:
Public Function PerformOperation(ByRef conn as ADODB.Connection, query as string) as Boolean
Dim rs as ADODB.Recordset
'This below is the timeout source
rs.Open conn, query, adOpenStatic, adLockReadOnly
rs.Close()
Set rs = Nothing
' [cut]: PerformOperation returns true if SELECT returns something
End Function
Edit:
There are other open states than adStateOpen, you should probably check against the closed state instead:
If (rs.State <> adStateClosed) Then rs.Close

Issue was solved editing this line:
If PerformOperation(conn, query) = True Then
like this
If PerformOperation(connOther, query) = True Then
Basically, method call has been moved to its own connection, declared exactly like the other one. Now it works, though I don't really know why: this is the result of some wild editing in an attempt to patch things up.

Related

"Invalid attempt to read when no data is present.", but "hasrows" is true

I wonder if anyone can shed any light on why I'm not getting data in this piece of code:
Private Sub RecoverUnsentOrderToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles RecoverUnsentOrderToolStripMenuItem.Click
' Find orders for this branch with status = "1" - created but not acked from the server
Dim myxDBReader As SqlDataReader
Dim myxDBcmd As SqlCommand
Dim query As String
query = "select * from orders where branch = #branch and status = 1;"
myxDBCmd = New SqlCommand(query, myDBCnn)
myxDBcmd.Parameters.Add("#branch", SqlDbType.VarChar).Value = BranchCode
myxDBReader = myDBCmd.ExecuteReader
If myxDBReader.HasRows Then
Do While myxDBReader.Read
Stop
Loop
End If
BranchCode and my database connection are public variables. When I run this code, it gets as far as the "Stop" and stops, but when I try to use the results, for example in the immediate window by trying to ? myxdbreader(0).tostring, I'm getting "Invalid attempt to read when no data is present" exceptions. When I hover over myxdbreader to view the results, I get a list of the rows, but cannot see the data in them.
This is inside a reasonably large (for me, but not massive) VB application which executes all manner of queries and retrieves data without any problems. The code is copied and pasted from another section of the code where it works quite well. The database connection is a single one, opened when the application is started and passed around as required. Another part of the application writes into this "orders" table without any problem, using the same database connection.
I have another toolstripmenu function which is identical in every respect except the query, in this case it is simply
select * from linkstatus where id=1
and that has the same issue - stops inside the do while dbreader.read loop so it has obviously found a row, but will not allow me to access the data in the way that I normally do.
Because it gets to the "Stop", I know that HasRows is indeed true, and it appears to have read the first row. The only thing I can see that is different is that this code is run from a Menu that I added to the form today, whereas all the rest of the code is run from a variety of buttons on the main form.
Everywhere I've looked up that error message, it appears to be because people have not executed "Read".
This is vb.net from Visual Studio 2019, accessing SQL Server 2018.
You are using a Public connection object, which as the comments state isn't the way to go. But most importantly, note that only one SqlDataReader can be associated with one SqlConnection, compounding the issue of a single shared connection.
SqlDataReader.Read Method
Only one SqlDataReader per associated SqlConnection may be open at a time, and any attempt to open another will fail until the first one is closed. Similarly, while the SqlDataReader is being used, the associated SqlConnection is busy serving it until you call Close.
It's probable that you already have an open Reader, and that is why you are seeing results for another data set.
As noted in the comments by #jmcilhenny, #Jeroen Mostert and #Jimi the root cause of this was using a single public database connection. I have changed the code now so that it connects and disconnects to the database each time I need to access it using a private connection, and it now works correctly.

Access pass through query

I have a very easy Stored Procedure in SQL (this is my first one!) and I can run it after researching how to accomplish this. Essentially, it deletes all the current records in a table and appends a new set of records. (I have read that having two queries in the same Stored Procedure may cause the problem I am having.)
This works very well the first time I run it. I make a change to the open table to see if it works the second time. It does not. I can even close the database, reopen and rerun the code and it still doesn't give me what I expect.
What I have figured out is that if I try to open the "CurrentProc" query in the Access window, I get an error that says "Pass-through query with ReturnsRecords property set to True did not return any records." That's fine, because after I hit "OK", the code will work again and give me the expected return.
My question is what can I do with the code below so that I can make whatever change I want to in a table, but have it all reset whenever I call the function without having to open the CurrentProc query and getting the error message. (Yes, I have tried opening the query with OpenQuery, but that didn't work either.)
Please understand that, while I have been an Access developer for (yikes!) nearly 20 years, this is my first time trying to use SQL stored procedures. Running this same query in Access takes about 45 minutes. Using the Stored procedure, it literally takes seconds! I will be adapting this for use with much larger recordsets and rewriting some Access code as SQL stored procedures to leverage this power, so any ideas you can provide will be greatly appreciated.
Dim qdf As dao.QueryDef
Dim dbs As dao.Database
Dim rst As dao.Recordset
Dim sSQL As String
strConnect = "ODBC;DRIVER={SQL Server}" _
& ";SERVER=ourserver\equipment" _
& ";DATABASE=BDS"
Set dbs = CurrentDb
Set qdf = dbs.QueryDefs("CurrentProc")
qdf.Connect = strConnect
qdf.SQL = "exec AppendtoFRPbyModel1"
DoCmd.OpenTable "FRP by Model1"
Set rst = Nothing
Set qdf = Nothing
Set dbs = Nothing
Josetta
Here's what worked:
I changed ReturnsRecords to "no" and that didn't do anything, HOWEVER, when I added "DoCmd.OpenQuery "CurrentProc" I did not receive an error (as I did before when I tried to run it outside of code) and I did get the expected results every time. Thank you!!

Proper code but can't insert to database

I have a Visual Basic project using Access database.I run a query but i don't see any new data on my database table.I don't have any exception or error.Instead of this the success messagebox is shown.
Here is my code:
Dim ID As Integer = 2
Dim TableNumber As Integer = 2
Dim OrderDate As Date = Format(Now, "General Date")
Dim TotalPrice As Double = 100.0
Dim ConnectionString As String = "myconnectionstring"
Dim con As New OleDb.OleDbConnection(ConnectionString)
Try
Dim InsertCMD As OleDb.OleDbCommand
InsertCMD = New OleDb.OleDbCommand("INSERT INTO Orders([ID],[TableNumber],[OrderDate],[TotalPrice]) VALUES(#ID,#TableNumber,#OrderDate,#TotalPrice);", con)
InsertCMD.Parameters.AddWithValue("#ID", ID)
InsertCMD.Parameters.AddWithValue("#TableNumber", TableNumber)
InsertCMD.Parameters.AddWithValue("#OrderDate", OrderDate)
InsertCMD.Parameters.AddWithValue("#TotalPrice", TotalPrice)
con.Open()
InsertCMD.ExecuteNonQuery()
MessageBox.Show("Successfully Added New Order",
"Success", MessageBoxButtons.OK, MessageBoxIcon.Information)
con.Close()
Catch ex As Exception
'Something went wrong
MessageBox.Show(ex.ToString)
Finally
'Success or not, make sure it's closed
If con.State <> ConnectionState.Closed Then con.Close()
End Try
What is the problem?
One of two things is happening: Either the data is not getting updated, or it's getting updated and you're missing it when you look for it afterward. ExecuteNonQuery normally returns the number of rows acted on. If it's zero, then it's possible it's failing without an exception. This might have something to do with a duplicate ID.
If it's inserting successfully, then you aren't seeing the new record for some reason. Maybe you're looking in the wrong place, or maybe the record isn't turning up in a query for some reason. I don't know of anything with "on duplicate update" by default, but if Access has that it could cause the problem.
Dim ConnectionString As String = "myconnectionstring"
I use ADO with Delphi to connect to Access so maybe a bit different, but I imagine with a connection string like that you're probably not even connecting to the database let alone updating it.
con.Open() is probably a function that will return a result of some kind indicating success or failure of connection. Failure of the Open and ExecuteNonQuery commands may not result in exceptions being raised, in which case your success message will display whether connection is achieved or not. Exceptions aren't the only type of error. Usually if a function returns a result indicating success or failure you should query that in a conditional statement to determine whether to process or show error message. wrapping swathes of code in exception handlers is lazy and in many cases not useful.
The only possible problem I could see is if the ID Field in the database is an auto increment field, otherwise it should work. Alternatively, I find the following method much simpler
Use a table adapter and data set to insert new data as follows
Me.OrdersTableadapter.insert(ID, tableNumber, orderDate, TotalPrice)
'NB: the parameters of this method will be in sequence of your table columns
Me.OrdersTableadapter.Fill(me.Dataset.Orderstableadapter)

Am I Leaking ADO.NET Connections?

Here is an example of my code in a DAL. All calls to the database's Stored Procedures are structured this way, and there is no in-line SQL.
Friend Shared Function Save(ByVal s As MyClass) As Boolean
Dim cn As SqlClient.SqlConnection = Dal.Connections.MyAppConnection
Dim cmd As New SqlClient.SqlCommand
Try
cmd.Connection = cn
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "proc_save_my_class"
cmd.Parameters.AddWithValue("#param1", s.Foo)
cmd.Parameters.AddWithValue("#param2", s.Bar)
Return True
Finally
Dal.Utility.CleanupAdoObjects(cmd, cn)
End Try
End Function
Here is the Connection factory (if I am using the correct term):
Friend Shared Function MyAppConnection() As SqlClient.SqlConnection
Dim cn As New SqlClient.SqlConnection(ConfigurationManager.ConnectionStrings("MyConnectionString").ToString)
cn.Open()
If cn.State <> ConnectionState.Open Then
' CriticalException is a custom object inheriting from Exception.
Throw New CriticalException("Could not connect to the database.")
Else
Return cn
End If
End Function
Here is the Dal.Utility.CleaupAdoObjects() function:
Friend Shared Sub CleanupAdoObjects(ByVal cmd As SqlCommand, ByVal cn As SqlConnection)
If cmd IsNot Nothing Then cmd.Dispose()
If cn IsNot Nothing AndAlso cn.State <> ConnectionState.Closed Then cn.Close()
End Sub
I am getting a lot of "Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding." error messages reported by the users. The application's DAL opens a connection, reads or saves data, and closes it. No connections are ever left open - intentionally!
There is nothing obvious on the Windows 2000 Server hosting the SQL Server 2000 that would indicate a problem. Nothing in the Event Logs and nothing in the SQL Server logs.
The timeouts happen randomly - I cannot reproduce. It happens early in the day with only 1 to 5 users in the system. It also happens with around 50 users in the system. The most connections to SQL Server via Performance Monitor, for all databases, has been about 74.
The timeouts happen in code that both saves to, and reads from, the database in different parts of the application. The stack trace does not point to one or two offending DAL functions. It's happened in many different places.
Does my ADO.NET code appear to be able to leak connections? I've goolged around a bit, and I've read that if the connection pool fills up, this can happen. However, I'm not explicitly setting any connection pooling. I've even tried to increase the Connection Timeout in the connection string, but timeouts happen long before the 300 second (5 minute) value:
<add name="MyConnectionString" connectionString="Data Source=MyServer;Initial Catalog=MyDatabase;Integrated Security=SSPI;Connection Timeout=300;"/>
I'm at a total loss already as to what is causing these Timeout issues. Any ideas are appreciated.
EDIT: This is a WinForms application.
One way to check for connection leaks is to add max pool size to the connection string, like:
"integrated security=SSPI;server=MyHost;Max Pool Size=1;"
In development I usually run with this setting (or size 2 if the application uses two connections simultaneously.)
From here:
Unlike Finalize, developers should call Dispose explicitly to free unmanaged resources. In fact, you should call the Dispose method explicitly on any object that implements it to free any unmanaged resources for which the object may be holding references.
SqlConnection, SqlCommand, SqlDataReader, etc... all implement IDisposable. If you enclose all these instances in a Using block, then Dispose will be called automatically, your connections will be closed, and you won't have to worry about issues like this. Fire up Reflector and take a look for yourself: (SqlConnection.Dispose)
protected override void Dispose(bool disposing)
{
if (disposing)
{
this._userConnectionOptions = null;
this._poolGroup = null;
this.Close();
}
this.DisposeMe(disposing);
base.Dispose(disposing);
}
This also makes the code shorter in that you don't have to manually add a Finally block to clean up your ADO.NET objects.
Using connection As SqlConnection("your connection string")
Using command As New SqlCommand("your sql", connection)
connection.Open()
Using dataReader As SqlDataReader = command.ExecuteReader()
'Your stuff here
End Using
End Using
End Using
Using the Using approach forces you to keep your ADO.NET objects local, which to me is a good thing.
You should always Dispose the Connection, regardless of its State:
'If cn IsNot Nothing AndAlso cn.State <> ConnectionState.Closed Then cn.Close()
If cn IsNot Nothing Then cn.Dispose()
I'm not sure it this could cause your timeouts but it certainly is an improvement.
How do your stored procedures perform outside of your application?
What if you moved the 'return true' out of your try/finally block in Save()?
Monitor DB connections in perf monitor to see if they grow.
But the first place I would look is your sprocs themselves - do they access tables in consistent order or might you be running into locks? For example, if proc1 manipulates table1 and then table2, while proc2 hits table2 and then table1 you might run into locking issues.
Is this a windows app or a web app?
Do the timeouts happen with simple stored procedures or just with more complicated ones?
Have you tried running a sql profiler trace to see if any queries really are taking a long time?
Also, have you tried converting to the "using" syntax, which ensures that the objects get closed and disposed of properly?

Return database messages on successful SQL execution when using ADO

I'm working on a legacy VB6 app here at work, and it has been a long time since I've looked at VB6 or ADO. One thing the app does is to executes SQL Tasks and then to output the success/failure into an XML file. If there is an error it inserts the text the task node.
What I have been asked to do is try and do the same with the other mundane messages that result from succesfully executed tasks, like (323 row(s) affected).
There is no command object being used, it's just an ADODB.Connection object. Here is the gist of the code:
Dim sqlStatement As String
Set sqlStatement = /* sql for task */
Dim sqlConn As ADODB.Connection
Set sqlConn = /* connection magic */
sqlConn.Execute sqlStatement, , adExecuteNoRecords
What is the best way for me to capture the non-error messages so I can output them? Or is it even possible?
The number of rows affected is returned through an optional second argument of the Connnection object's Execute method
Dim num As Long
sqlConn.Execute sqlStatement, num, adExecuteNoRecords
MsgBox num & " records were affected"
Besides having a generic error handler in your routines the ADO connection object has an Errors collection. After performing some action check the errors for a count > 0 and if it is you need to iterate the collection and log all the errors. There is a Clear method if you want to continue after logging.
After making a quick test project I found that declaring my variable using WithEvents VB adds the InfoMessage event. I ran a DBCC CHECKDB command and the InfoMessage event fired once. The pConnection variable had 284 errors in it with all the other messages.
note: the Connection.Errors collection is 0 based.
I think the rows affected is a function of the Query Analyzer/Enterprise manager, and not something returned through the API.
If I recall correctly, using classic ADO, we had to do a MoveLast then a MoveFirst to force all of the records to come over the wire, then do a count of the Recordset.
I also remember something about which cursor type being used affecting the count of records coming back.
Other than that, are you trying to grab the print statement... It seems like you are using no stored procedures, so beyond count, what are you expecting to get?

Resources