I'm populating a listview with a list of databases on a selected SQL instance, then retrieving a value from each database (It's internal product version, column doesn't always exist) I'm calling the below function to populate the second column of the listview:
item.SubItems.Add(DBVersionCheck(serverName, database.Name))
Function DBVersionCheck(ByVal SelectedInstance As String, ByVal SelectedDatabase As String)
Dim m_Connection As New SqlConnection("Server=" + SelectedInstance + ";User Id=sa;Password=password;Database=" + SelectedDatabase)
Dim db_command As New SqlCommand("select Setting from SystemSettings where [Setting] = 'version'", m_Connection)
Try
m_Connection.Open()
Return db_command.ExecuteScalar().trim
m_Connection.Dispose()
Catch ex As Exception
'MessageBox.Show(ex.Message)
Return "NA"
Finally
m_Connection.Dispose()
End Try
End Function
This works fine except it's creating a connection to each database and leaving it open.
My understanding is the close()\dispose() releases only the connection from the pool in ado rather than the actual connection to sql.
How would I close the actual connections after I've retrieved the value?
Leaving these open will create hundreds of connections to databases that will probably not be used for that session.
Add Pooling=false to your connection string. That should take care of it.
Two approaches you can use:
1 - Call the ClearAllPools or ClearPool method. You may prefer this so that you can make use of pooling with your application, but then clear the pools when you are done.
2 - Adjust your connection string to not pool the connection. Go here and search for "connection pooling values within the ConnectionString" for more info.
Related
Perhaps this is the complete wrong way of doing things, and if so could you perhaps point me in the correct (elegant) way. :)
I have a module in my vb.net project. The module deals with db connections. The idea is for other modules to make use of this module when connections need to be created.
For each database type I have function that opens the db connection. As an example I have this function that opens an oracle connection.
Friend Function OracleConnection(ByVal HostAddress As String, ByVal PortNumber As String, ByVal DBName As String, ByVal UserId As String, ByVal Password As String) As OracleConnection
Try
OracleConnection = New OracleConnection("Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=" & HostAddress & ")(PORT=" & PortNumber & "))(LOAD_BALANCE=yes)(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=" & DBName & ")(FAILOVER_MODE=(TYPE=select)(METHOD=BASIC)(RETRIES=180)(DELAY=5))));User Id=" & UserId & ";Password=" & Password & ";")
OracleConnection.Open()
OracleConnection = OracleConnection
Catch ex As OracleException
MsgBox(ex.Message, MsgBoxStyle.Critical)
OracleConnection = Nothing
End Try
Return OracleConnection
End Function
Creating the connetion seems to work fine. The problem I have is that I am now not sure how to close the connection that got created by this function.
Option 1
You should always close OracleConnection objects by calling Close or Dispose, or by using the OracleConnection object within a Using statement.
Otherwise, the garbage collection might not free them immediately. Such delays can cause errors if the maximum number of connections is reached while a number of connections are waiting to be deleted by the garbage collector.
By contrast, closing the connections by calling Close uses native resources more efficiently, enhancing scalability and improving overall application performance. To ensure that connections are always closed, open the connection inside of a Using block.
Public Sub InsertRow(ByVal connectionString As String)
Dim queryString As String = "INSERT INTO Dept (DeptNo, Dname, Loc) values (50, 'TECHNOLOGY', 'DENVER')"
Using connection As New OracleConnection(connectionString)
Dim command As New OracleCommand(queryString)
command.Connection = connection
Try
connection.Open()
command.ExecuteNonQuery()
Catch ex As Exception
Console.WriteLine(ex.Message)
End Try
End Using
End Sub
For more information, visit MSDN.
Option 2
Take a look at Oracle's recommended best practices:
http://www.oracle.com/technetwork/topics/dotnet/ow2011-bp-performance-deploy-dotnet-518050.pdf
You automatically get a connection pool when you create an OracleConnection. For most middle tier applications you will want to take advantage of that. You will also want to tune your pool for a realistic workload by turning on Performance Counters in the registry.
Please see the ODP.NET online help for details on connection pooling. Pool settings are added to the connection string.
Another issue people run into a lot with OracleConnections is that the garbage collector does not realize how truly resource intensive they are and does not clean them up promptly. This is compounded by the fact that ODP.NET is not fully managed and so some resources are hidden from the garbage collector.
Hence the best practice is to Close() AND Dispose() all Oracle ODP.NET objects (including OracleConnection) to force them to be cleaned up.
Credits go to Christian Shay this answer.
This may have been answered but my search hasn't found what I was looking for.
Basically, I am developing an application which allows the user to build a query at design time, i.e. for users with no prerequisite knowledge of SQL
The application thus far allows the user to select which table(s) from the database they wish to start querying (I won't go into the details of the rest for now)
My confusion is this; I already have the connection to the database in a subroutine which obtains the schema information and filters it to display only the available tables within the database, which then compiles the data into a listbox, here is that sub:
Public Sub getSchemaInfo()
Dim ds As New DataSet
Dim dt As New DataTable
Dim con As New OleDbConnection
Dim strDatabaseLocation As String = Application.StartupPath
Dim da As New OleDbDataAdapter
Dim i As Integer
'ds.Tables.Add(dt)
con.ConnectionString = "Provider=microsoft.jet.oledb.4.0; data source = " & strDatabaseLocation & _
"\EmployeeDepartment.mdb"
'clear listbox of any data first
frmAddTable.lbTables.Items.Clear()
'Try catch block used to handle connection errors gracefully
Try
con.Open()
'Accessing methods to obtain schema information
dt = con.GetOleDbSchemaTable(OleDb.OleDbSchemaGuid.Tables, New Object() _
{Nothing, Nothing, Nothing, "TABLE"})
'loop datatable to store schema information within it
For i = 0 To dt.Rows.Count - 1
'compile lbtables with a list of available tables from the database
frmAddTable.lbTables.Items.Add(dt.Rows(i)!TABLE_NAME.ToString())
Next
Catch ex As Exception
MessageBox.Show(ex.Message.ToString(), "Data Load Error", MessageBoxButtons.OK,
MessageBoxIcon.Exclamation)
End Try
con.Close()
As you can see, at the very top is all the information regarding the connection to the database and loading of the information into the dataset.
My question is this; Whenever I need to gain access to the database and any information within it, will I have to perform all the connection process (oledbconnection, etc..)
or is there I way I can create a class for the connection functions and simply reference them whenever I need to connect?
For example, I now am in the process of creating another sub which gathers the columns, based on the tables chosen in the listbox, and displays it back onto the main form in the relevant checklistbox, again, connecting to the database, therefore would I need to perform all of the connection processes?
Any information would be very useful, thank you!
It is a standard approach to separate your DAL ( Data Access Logic ) from your Business Logic. I would definitely create a separate class for connecting to the database and executing the queries that would bring back the results that you can then either Bind to a control or iterate over inside a loop.
You might even want to look into using EF ( Entity Framework ) or my favorite LINQ to SQL to help in following a standard Design Pattern. By using a framework like EF or L2S you can leverage their ability to cache objects and return back strongly typed objects versus loosely typed. Strongly typed objects give you intelisense and are less prone to common mistakes like misspelling a field from a DataTable.
I've inherited an application that uses a global database sqlconnection object in order to access the database from every form in the application. The connection is established when the application starts.
I think to have the connection open all the time it's not good practice and I would prefer to change it so I would open the database connection and close it every time I need to access the database.
So I would like to know if I am in the right here.
This is what I would use instead, any suggestion for improvement is welcome:
Public Sub UpdateDatabase(ByVal command As SqlCommand, ByRef NumError As Double, ByRef DescError As String)
Using connection As New SqlConnection(connectionString)
Try
command.ExecuteNonQuery()
command.Dispose()
NumError = 0
DescError = ""
Catch ex As Exception
NumError = Err.Number
DescError = Err.Description
End Try
End Using
End Sub
I send the SqlCommand object to the method instead of a query string because I can add parameters to an SqlCommand object.
the way you are handling the connection with a using is fine and connection will always be closed and disposed for you.
not really good the way you pass the command from the caller, no database engine isolation. For the parameters you can simply pass a list of names and values and add the parameters in your class above and not in the 100.000 places where you call this code.
don't forget to associate the newly opened connection to the command or it will not work.
some people also put another using around the command so command is disposed, inside the using for the connection...
I'm following code I found on another site. Here's the basics of my code:
Dim SQL As String
Dim connString As String
connString = "ODBC;DSN=DB01;UID=;PWD=;Database=MyDatabase"
SQL = "Select * from SomeTable"
With Worksheets("Received").QueryTables.Add(Connection:=connString, Destination:=Worksheets("Received").Range("A5"), SQL:=SQL)
.Refresh
End With
End Sub
The problem with doing this is every single time they hit the button assigned to this it creates a new connection and doesn't ever seem to drop it. I open the spreadsheet after testing and there are many versions of the connection listed under Connections.
Connection
Connection1
Connection2
I can't seem to find a way to close or delete the connections either. If I add ".delete" after ".Refresh" I get a 1004 error. This operation cannot be done because the data is refreshing in the background.
Any ideas how to close or delete the connection?
You might ask yourself why you're creating a QueryTable every time in your code. There are reasons to do it, but it usually isn't necessary.
QueryTables are more typically design-time objects. That is, you create your QueryTable once (through code or the UI) and the you Refresh the QueryTable to get updated data.
If you need to change the underlying SQL statement, you have some options. You could set up Parameters that prompt for a value or get it from a cell. Another option for changing the SQL is changing it in code for the existing QueryTable.
Sheet1.QueryTables(1).CommandText = "Select * FROM ...."
Sheet1.QueryTables(1).Refresh
You can select different columns or even different tables by changing CommandText. If it's a different database, you'll need a new connection, but that's pretty rare.
I know that doesn't answer your question directly, but I think determining whether you really need to add the QueryTable each time is the first step.
For more on Parameters, see http://dailydoseofexcel.com/archives/2004/12/13/parameters-in-excel-external-data-queries/ It's for 2003, so there are few inconsistencies with later versions. The basics are the same, you just may need to learn about the ListObject object if you're using 2007 or later.
I had the same issue. The previous answer while a definite step in the right direction is a PITA.
It did however allow me to refine my search and the winner is...
http://msdn.microsoft.com/en-us/library/bb213491(v=office.12).aspx
i.e. for your existing QueryTable Object just do this:
.MaintainConnection = False
Works ever so swell. No more Access DB lock file after the data is refreshed.
You should declare the connection as a separate object then you can close it once the database query is complete.
I don't have the VBA IDE in front of me, so excuse me if there are any inaccuracies, but it should point you in the right direction.
E.g.
Dim SQL As String
Dim con As connection
Set con = New connection
con.ConnectionString = "ODBC;DSN=DB01;UID=;PWD=;Database=MyDatabase"
Worksheets("Received").QueryTables.Add(Connection:=con, Destination:=Worksheets("Received").Range("A5"), SQL:=SQL).Refresh
con.close
set con = nothing
I've found that by default new connections created this way are called "Connection". What I am using is this snippet of code to remove the connection but retain the listobject.
Application.DisplayAlerts = False
ActiveWorkbook.Connections("Connection").Delete
Application.DisplayAlerts = True
It can easily be modified to remove the latest added connection (or if you keep track of the connections by their index).
Application.DisplayAlerts = False
ActiveWorkbook.Connections(ActiveWorkbook.Connections.Count).Delete
Application.DisplayAlerts = True
Instead of adding another query table with the add method, you can simply update the CommandText Property of the connection. However you have to be aware that there is a bug when updating the CommandText property of an ODBC connection. If you temporarily switch to an OLEDB connection, update your CommandText property and then switch back to ODBC it does not create the new connection. Don't ask me why... this just works for me.
Create a new module and insert the following code:
Option Explicit
Sub UpdateWorkbookConnection(WorkbookConnectionObject As WorkbookConnection, Optional ByVal CommandText As String = "", Optional ByVal ConnectionString As String = "")
With WorkbookConnectionObject
If .Type = xlConnectionTypeODBC Then
If CommandText = "" Then CommandText = .ODBCConnection.CommandText
If ConnectionString = "" Then ConnectionString = .ODBCConnection.Connection
.ODBCConnection.Connection = Replace(.ODBCConnection.Connection, "ODBC;", "OLEDB;", 1, 1, vbTextCompare)
ElseIf .Type = xlConnectionTypeOLEDB Then
If CommandText = "" Then CommandText = .OLEDBConnection.CommandText
If ConnectionString = "" Then ConnectionString = .OLEDBConnection.Connection
Else
MsgBox "Invalid connection object sent to UpdateWorkbookConnection function!", vbCritical, "Update Error"
Exit Sub
End If
If StrComp(.OLEDBConnection.CommandText, CommandText, vbTextCompare) <> 0 Then
.OLEDBConnection.CommandText = CommandText
End If
If StrComp(.OLEDBConnection.Connection, ConnectionString, vbTextCompare) <> 0 Then
.OLEDBConnection.Connection = ConnectionString
End If
.Refresh
End With
End Sub
This UpdateWorkbookConnection subroutine only works on updating OLEDB or ODBC connections. The connection does not necessarily have to be linked to a pivot table. It also fixes another problem and allows you to update the connection even if there are multiple pivot tables based on the same connection.
To initiate the update just call the function with the connection object and command text parameters like this:
UpdateWorkbookConnection ActiveWorkbook.Connections("Connection"), "exec sp_MyAwesomeProcedure"
You can optionally update the connection string as well.
If you want to delete if right after refresh you should do the refresh not in the background (using first parameter -> Refresh False) so that you have proper sequence of actions
Try setting the QueryTable.MaintainConnection property to False...
"Set MaintainConnection to True if the connection to the specified data source is to be maintained after the refresh and until the workbook is closed. The default value is True! And there doesn't seem to be a UI check box for this (Read/write Boolean)"
Still relevant years later...battling the same issue and this is the most helpful thread out there. My situation is a variant of the above and I will add my solution when I find it.
I am using an Access database for my data source and establish a querytable on a new sheet. I then add two more new sheets and try to establish a querytable using the same connection on each of them, but to a different Access table. The first querytable works just fine and I use .QueryTables(1).Delete and setting the querytable object to Nothing to make it disconnected.
However, the next sheet fails on establishing a new querytable using the same connection, which was not closed. I suspect (and will add the solution below) that I need to drop the connection before deleting the querytable. Rasmus' code above looks like the likely solution.
My application requires a user to log in and allows them to edit a list of things. However, it seems that if the same user always logs in and out and edits the list, this user will run into a "System.Data.SqlClient.SqlException: Timeout expired." error. I've read a comment about it possibly caused by uncommitted transactions. And I do have one going in the application.
I'll provide the code I'm working with and there is an IF statement in there that I was a little iffy about but it seemed like a reasonable thing to do.
I'll just go over what's going on here, there is a list of objects to update or add into the database. New objects created in the application are given an ID of 0 while existing objects have their own ID's generated from the DB. If the user chooses to delete some objects, their IDs are stored in a separate list of Integers. Once the user is ready to save their changes, the two lists are passed into this method. By use of the IF statement, objects with ID of 0 are added (using the Add stored procedure) and those objects with non-zero IDs are updated (using the Update stored procedure). After all this, a FOR loop goes through all the integers in the "removal" list and uses the Delete stored procedure to remove them. A transaction is used for all this.
Public Shared Sub UpdateSomethings(ByVal SomethingList As List(Of Something), ByVal RemovalList As List(Of Integer))
Using DBConnection As New SqlConnection(conn)
DBConnection.Open()
Dim MyTransaction As SqlTransaction
MyTransaction = DBConnection.BeginTransaction()
Try
Using MyCommand As New SqlCommand()
MyCommand.Transaction = MyTransaction
MyCommand.CommandType = CommandType.StoredProcedure
For Each SomethingItem As Something In SomethingList
MyCommand.Connection = DBConnection
If SomethingItem.ID > 0 Then
MyCommand.CommandText = "UpdateSomething"
Else
MyCommand.CommandText = "AddSomething"
End If
MyCommand.Parameters.Clear()
With MyCommand.Parameters
If MyCommand.CommandText = "UpdateSomething" Then
.Add("#id", SqlDbType.Int).Value = SomethingItem.ID
End If
.Add("#stuff", SqlDbType.Varchar).Value = SomethingItem.Stuff
End With
MyCommand.ExecuteNonQuery()
Next
MyCommand.CommandText = "DeleteSomething"
For Each ID As Integer In RemovalList
MyCommand.Parameters.Clear()
With MyCommand.Parameters
.Add("#id", SqlDbType.Int).Value = ID
End With
MyCommand.ExecuteNonQuery()
Next
End Using
MyTransaction.Commit()
Catch ex As Exception
MyTransaction.Rollback()
'Exception handling goes here '
End Try
End Using
End Sub
There are three stored procedures used here as well as some looping so I can see how something can be holding everything up if the list is large enough.
I'm using Visual Studio 2008 to debug and am using SQL Server 2000 for the DB.
Edit: I still seem to be getting this error. I've even removed the whole transaction thing and I still encounter it. At this point, I'm assuming there is some kind of leak happening here. I've tried not using the USING statements and explicitly tell the command and connection to dispose itself but no dice. Memory usage by SQL Server also increases quite a bit if this method is called a lot in a short period of time.
I've read that increasing the CommandTimeout property of the SQLCommand would help. I'm wondering if there are any big disadvantages or consequences from doing so.
I would suggest using the following, that way Dispose will always be called and be Rolledback in every non-committed case.
using (SqlConnection sqlCn = new SqlConnection())
{
using (SqlTransaction myTrans = sqlCn.BeginTransaction())
{
...
myTrans.Commit();
}
}
Also, I don't believe you need to make a new SqlCommand for every execution. Just maintain the same one and update the CommandText and Parameters.
If you have a large number of commands, you may want to build them all before opening the connection. After you start the transaction and open the connection, spin through and execute them.
You probably want to use TransactionScope
Using _tx as New System.Transactions.TransactionScope(<add your own timeout here>)
'Do all your sql work'
If _noErrors Then
_tx.Complete()
End If
End Using
With the transaction scope, you can set a timeout of up to 20 minutes without modifying server settings.
I believe I have managed to solve the problem. I have modified the application so that unnecessary calls to the database are not made (i.e. unchanged objects do not need to be updated again) and increased the CommandTimeout property for the SQLCommand object. So far, no problems.
Big thanks for suggestions too.