I have a whole lot of stored procedures in a SQL Server database that need to be executed in sequence.
I have them setup in a table that have multiple sequences each containing a max of 5 stored procedures - so basically 5x threads:
I would like loop through each sequence and kick off each sequence's stored procedures at the same time. Then when all the stored procedures have completed, the next sequence of stored procedures can be kicked off.
I will be using SQL commands in .NET to kick of each stored procedure, but I need a way for them to all start at the same time and have the process wait for all to finish before moving on to the next sequence.
The idea of this is so that I can save time on processing data. If each stored procedure runs 2 minutes, running 5 at a time will take 2 minutes instead of 10 minutes.
Now I was told TPL would be a good approach. This is my first time working with multi-threading / TPL.
Below is a class that contain the properties and a function to execute a SQL Server stored procedure:
Public Class clsSQLStoredProcedure
Private mConnection As SqlClient.SqlConnection
Private mCommand As SqlClient.SqlCommand
Private mSQLStoredProcedure As String
Private mName As String
Private mStatus As String = "Pending"
Private mFeedback As DataTable
Public Property Connection() As SqlClient.SqlConnection
Set(ByVal o As SqlClient.SqlConnection)
mConnection = o
End Set
Get
Return mConnection
End Get
End Property
Public Property SQLStoredProcedure() As String
Set(ByVal o As String)
mSQLStoredProcedure = o
End Set
Get
Return mSQLStoredProcedure
End Get
End Property
Public Property Name() As String
Set(ByVal o As String)
mName = o
End Set
Get
Return mName
End Get
End Property
Public ReadOnly Property Status() As String
Get
Return mStatus
End Get
End Property
Public ReadOnly Property Feedback() As DataTable
Get
Return mFeedback
End Get
End Property
Public Function ExecuteSQLStoredProcedure()
On Error GoTo Errorhandler
Debug.Print(mName & " Start # " & Now().ToString)
mStatus = "Running"
If mConnection.State <> ConnectionState.Open Then mConnection.Open()
mCommand = New SqlClient.SqlCommand(mSQLStoredProcedure, mConnection)
mCommand.CommandTimeout = 0
mCommand.ExecuteNonQueryAsync()
mStatus = "Completed"
Debug.Print(mName & " Completed # " & Now().ToString)
Exit Function
Errorhandler:
mStatus = "Failed"
Debug.Print(mName & " Failed # " & Now().ToString & " - " & Err.Description)
End Function
End Class
This is the code I use to perform tasks (TPL) to start 3 stored procedures at the same time:
Imports System.Threading
Imports System.Threading.Tasks
Module modThreading
Public Sub TestThreading()
Dim oSP1 As New clsSQLStoredProcedure
oSP1.SQLStoredProcedure = "EXEC CURRO_DW.conform.usp_ETLFactCollectorActivity -99"
oSP1.Connection = gGDM.Database.Connection
oSP1.Name = "usp_ETLFactCollectorActivity"
'oSP1.ExecuteSQLStoredProcedure()
Task.Run(action:=oSP1.ExecuteSQLStoredProcedure())
Dim oSP2 As New clsSQLStoredProcedure
oSP2.SQLStoredProcedure = "EXEC CURRO_DW.conform.usp_ETLDimPerson -99"
oSP2.Connection = gGDM.Database.Connection
oSP2.Name = "usp_ETLDimPerson"
'oSP2.ExecuteSQLStoredProcedure()
Task.Run(action:=oSP2.ExecuteSQLStoredProcedure())
Dim oSP3 As New clsSQLStoredProcedure
oSP3.SQLStoredProcedure = "SELECT 1"
oSP3.Connection = gGDM.Database.Connection
oSP3.Name = "TEST"
'oSP3.ExecuteSQLStoredProcedure()
Task.Run(action:=oSP3.ExecuteSQLStoredProcedure())
MsgBox("Done")
End Sub
End Module
However they still seem to run one after the other even though some are suppose to run instant while other take about 1min.
Don't keep your connection open for the life of your application. You should open and close a database connection as late and early as possible, respectively. A database connection runs on a single thread so the first task, although being run asynchronously, will block the second, et cetera.
So create a new connection for each stored procedure and perhaps WaitAll at the end. You can put the Tasks in a collection to do this.
Dim gGDM1 As New DatabaseContext()
Dim gGDM2 As New DatabaseContext()
Dim gGDM3 As New DatabaseContext()
Try
Dim oSP1 As New clsSQLStoredProcedure
oSP1.SQLStoredProcedure = "EXEC CURRO_DW.conform.usp_ETLFactCollectorActivity -99"
oSP1.Connection = gGDM1.Database.Connection
oSP1.Name = "usp_ETLFactCollectorActivity"
Dim oSP2 As New clsSQLStoredProcedure
oSP2.SQLStoredProcedure = "EXEC CURRO_DW.conform.usp_ETLDimPerson -99"
oSP2.Connection = gGDM2.Database.Connection
oSP2.Name = "usp_ETLDimPerson"
Dim oSP3 As New clsSQLStoredProcedure
oSP3.SQLStoredProcedure = "SELECT 1"
oSP3.Connection = gGDM3.Database.Connection
oSP3.Name = "TEST"
Dim tasks As New List(Of Task)()
tasks.Add(Task.Run(oSP1.ExecuteSQLStoredProcedure))
tasks.Add(Task.Run(oSP2.ExecuteSQLStoredProcedure))
tasks.Add(Task.Run(oSP3.ExecuteSQLStoredProcedure))
Task.WaitAll(tasks.ToArray())
Finally
gGDM1.Dispose()
gGDM2.Dispose()
gGDM3.Dispose()
MsgBox("Done")
End Try
You need to modify the Dim gGDM As New DatabaseContext() lines to create a new context, however you have done it globally, but instead for each call. They are immediately disposed of when you're done.
Related
I am simulating an ATM in Visual Basic. I have a table called Authentication in SQL. The Table contains two columns: The NUM_CARD column and the PIN_CARD column. I need to match row (0) column 1, row (1) column (1), row (2) column (1), and so on with the other rows as the card IDs are inserted. How can I do that? Thanks in advance.
The class DBConnection is the following:
Imports System
Imports System.Data.Sql
Imports System.Data.SqlClient
Public Class clsDBConnection
'Class variables'
Public cn As SqlConnection
Public cmd As SqlCommand
Public dr As SqlDataReader
'Constructor of the Connection class that creates the connection'
Sub New()
Try
cn = New SqlConnection("Data Source=JOVALLES-PC\SQLSERVEREX;Initial Catalog=SigmasBank;Integrated Security=True")
cn.Open()
Catch ex As Exception
MsgBox("Error connecting due to:: " + ex.ToString)
End Try
End Sub
'Returns true or false if the record exists or not in the database'
Function validationAutentication_p1(ByVal NUM_CARD As String) As Boolean
Dim result As Boolean = False
Try
cmd = New SqlCommand("Select * from Autentication where NUM_CARD='" & NUM_CARD & "'", cn)
dr = cmd.ExecuteReader
If dr.HasRows Then
result = True
End If
dr.Close()
Catch ex As Exception
MsgBox("Error in the procedure: " + ex.ToString)
End Try
Return result
End Function
Function validationAutentication_p2(ByVal PIN_CARD As String) As Boolean
Dim result As Boolean = False
Try
cmd = New SqlCommand("Select * from Autentication where PIN_CARD='" & PIN_CARD & "'", cn)
dr = cmd.ExecuteReader
If dr.HasRows Then
result = True
End If
dr.Close()
Catch ex As Exception
MsgBox("Error in the procedure: " + ex.ToString)
End Try
Return result
End Function
End Class
Insert Card ID Form:
Public Class FRM_InsertCardID
Public conn As New clsDBConnection
Private Sub BTN_Ok_Click(sender As Object, e As EventArgs) Handles BTN_Ok.Click
If TXB_CardID.Text.Length = 0 Then
MsgBox("Please fill in field.")
ElseIf TXB_CardID.Text.Length > 0 And TXB_CardID.Text.Length < 16 Then
MsgBox("Your Card ID must be 16 digits.")
ElseIf conn.validationAutentication_p1(TXB_CardID.Text) = False Then
MsgBox("The Card ID doesn't exist.")
Else
FRM_PIN.Show()
Me.Hide()
TXB_CardID.Text = ""
End If
End Sub
Insert PIN form:
Public Class FRM_PIN
Public conn As New clsDBConnection
Private Sub BTN_Ok_Click(sender As Object, e As EventArgs) Handles BTN_Ok.Click
If TXB_PIN.Text.Length = 0 Then
MsgBox("Please fill in field.")
ElseIf TXB_PIN.Text.Length > 0 And TXB_PIN.Text.Length < 4 Then
MsgBox("Your PIN must be 4 digits.")
ElseIf conn.validationAutentication_p2(TXB_PIN.Text) = False Then
MsgBox("Incorrect PIN Please try again.")
Else
FRM_Transaction.Show()
Me.Hide()
TXB_PIN.Text = ""
End If
End Sub
Not sure if typo causing issue otherwise?? - - Authentication
"I have a table called Authentication in SQL. "
" cmd = New SqlCommand("Select * from Autentication where PIN_CARD='" & PIN_CARD & "'", cn)"
Let's start with clsDBConnection. You do not need to import System. That is there by default. System.Data.Sql is never used. Get rid of that too.
One would think that this class is about a database connection. It is not. It contains code for authentication. So rename; something like DataAccess.
Never make connections, commands and readers class level variables. These database objects need to be closed and disposed so the class is not where to declare them. They need to be local variables, local to the method where they are used.
Never, never open a connection until directly before it is used. Ideally the line before an .Execute... method is called. Be sure it is also closed and disposed as soon as possible. Your code opens a connection and leaves it flapping in the breeze.
What you can do in a DataAccess class is make your connection string a Private class level variable. Private cnString as String = ...
I can't see where you would need a custom constructor at all. Just get rid of Sub New() I have made the 2 methods in your class Shared This data is shared by all instances of the class and you do not have declare an instance of the class to use these methods. You can call shared methods just by referencing the name of the class and the method. Also the conString is Shared because it is used by shared methods.
I decided that the pin number is not necessarily unique since they only go up to 9999. That is why I used 2 parameters for the second method.
Note:
I had to guess at the datatype and field size of the SqlParameters. Check your database and adjust the code accordingly.
Public Class FRM_InsertCardID
Private Sub BTN_Ok_Click(sender As Object, e As EventArgs) Handles BTN_Ok.Click
If TXB_CardID.Text.Length = 0 Then
MsgBox("Please fill in field.")
'Don't give the user any information on what a proper card ID consists of
Return
End If
If DataAccess.validationAutentication_p1(TXB_CardID.Text) = False Then
MsgBox("The Card ID doesn't exist.")
Else
FRM_PIN.Show()
'It appears you are using the default instance of FRM_PIN
FRM_PIM.CardID = TXB_CardID.Text
TXB_CardID.Text = ""
Me.Hide()
End If
End Sub
End Class
Public Class FRM_PIN
Friend CardID As String
Private Sub BTN_Ok_Click(sender As Object, e As EventArgs) Handles BTN_Ok.Click
If TXB_PIN.Text.Length = 0 Then
MsgBox("Please fill in field.")
Return 'Exits the sub
End If
If DataAccess.validationAutentication_p2(CardID, TXB_PIN.Text) = False Then
MsgBox("Incorrect PIN Please try again.")
Else
TXB_PIN.Text = ""
FRM_Transaction.Show()
Me.Hide()
End If
End Sub
End Class
Public Class DataAccess
Private Shared conString As String = "Data Source=JOVALLES-PC\SQLSERVEREX;Initial Catalog=SigmasBank;Integrated Security=True"
Public Shared Function validationAutentication_p1(ByVal NUM_CARD As String) As Boolean
Dim result = False
Using cn As New SqlConnection(conString),
cmd As New SqlCommand("Select * from Autentication where NUM_CARD= #NumCARD;", cn)
cmd.Parameters.Add("#NumCard", SqlDbType.VarChar, 16).Value = NUM_CARD
cn.Open()
Using dr = cmd.ExecuteReader
If dr.HasRows Then
result = True
End If
End Using
End Using
Return result
End Function
Public Shared Function validationAutentication_p2(ByVal CardID As String, ByVal PIN_CARD As String) As Boolean
Dim result = False
Using cn As New SqlConnection(conString),
cmd As New SqlCommand("Select * From Autentication where NUM_CARD = #NumCard AND PIN_CARD=#PinCard;", cn)
cmd.Parameters.Add("#NumCard", SqlDbType.VarChar, 100).Value = CardID
cmd.Parameters.Add("#PinCard", SqlDbType.VarChar, 4).Value = PIN_CARD
cn.Open()
Using dr = cmd.ExecuteReader()
If dr.HasRows Then
result = True
End If
End Using
End Using
Return result
End Function
End Class
Access 2003 / SQL Server - how can I update Access 2003 MDB (Connect property) to point to a different SQL Server database? The new SQL Server database is on the same instance as the old one.
I have several MS Access 2003/SQL Server applications that I manage. All of them dynamically attach to the correct database at startup. Some of them even connect to multiple databases on different servers during the start up sequence. All of them use the same basic vba routine to actually dynamically attach a table to the correct server. This is not my code, I found it by googling around the internet, but I have lost the reference to it now, so apologies in advance to the authors.
Before showing the code, to put it in context, I normally have a form "frmInitApp" with a data source that is a local config table, with a field named "ID". I start the access application from the AutoExec macro which opens this form with a filter of "ID=1". I have other forms to manipulate this config table and change the IDs around, so to switch between production and test I just change which entry has ID=1.
I also have another local table, tableList, with a list of Access tables that I want to connect dynamically to a SQL Server. Most applications have another field in this table for the SQL Server table name (so they don't have to be the same) - some applications have an additional field to specify which database. But the more complex the more other spaghetti you need - I often end up with another table of connection strings to all the separate databases I might connect to etc etc. To keep it simple just have the connection string in a field in the config table that is the datasource to frmInitApp.
We get started with the current event on frmInitApp.
Private Sub Form_Current()
If Me.Filter = "" Then 'If nobody has told us what record to use then use id=1
Me.Filter = "[ID]=1"
configID = 1
Else
configID = CInt(Mid(Me.Filter, 6)) 'We are assuming the load criteria are "[ID]=..."
End If
Me.messages = "Connecting to databases ..."
DoCmd.Hourglass True
Me.stage = "InitialStartup" 'Set the stage which is to be executed during timer phase
Me.TimerInterval = 100 'We set the time to go off to so we can let autoexec finish and let us control focus
End Sub
and then in the timer we can link to the tables via an attach table function with I'll put further down the answer. Note also that we relink pass through queries as well so they point to the new database also. Also note that we start Open a new form a login one fore users as soon as we have attached to the first table. I don't show the conclusion where will probably have to validate username and password against the attached table when its all done, but its trivial to figure out anyway.
Private Sub Form_Timer()
Dim conn As ADODB.Connection
Dim dbRs As ADODB.Recordset
Dim dbOK As Boolean
Dim SQL As String
Dim startedLogon As Boolean
Me.TimerInterval = 0
Select Case Me.stage
Case "InitialStartup"
Set conn = CurrentProject.Connection
startedLogon = False
If CurrentProject.AllForms("frmLogon").IsLoaded Then
'If its already loaded this NOT the first time through, but still need to logon ...
If Form_frmLogon.configID = configID Then
startedLogon = True 'unless its the same config
End If
End If
dbOK = True
Set dbRs = New ADODB.Recordset
dbRs.Open "SELECT localname,servername FROM tableList", conn
While dbOK And Not dbRs.EOF
'PLEASE NOTE - WHILST THEORETICALLY "localname" and "servername" could be different the migration process
'requires that they be the same. Do not consider changing this until after migration is completed
dbOK = AttachTable(dbRs("localname"), "dbo." & dbRs("servername"))
dbRs.MoveNext
If Not startedLogon And dbOK Then
DoCmd.Close acForm, "frmLogon" '#554 Just in case its alread open - we need to pick up new params
DoCmd.OpenForm "frmLogon", , , , , , Nz(Me.lastUserId, "") & ":" & configID
Form_frmLogon.SetFocus '#748 Give it focus
startedLogon = True
End If
Wend
dbRs.Close
If dbOK Then
Me.messages = "Relinking Common Queries ..."
DoEvents
Dim qd As DAO.QueryDef, cs As String
cs = getStrConnDAO 'get the DAO connection string
For Each qd In CurrentDb.QueryDefs
If Len(qd.Connect & vbNullString) > 0 Then
qd.Connect = cs
End If
Next
End If
Me.messages = "Awaiting User Log On"
DoCmd.Hourglass False
DoEvents
... the rest just managing logon
End Sub
The attached table function
'//Name : AttachTable
'//Purpose : Create a linked table to SQL Server without using a DSN
'//Parameters
'// stLocalTableName: Name of the table that you are creating in the current database
'// stRemoteTableName: Name of the table that you are linking to on the SQL Server database
Private Function AttachTable(stLocalTableName As String, stRemoteTableName As String)
Dim td As TableDef
Dim stConnect As String
Me.messages = "Connecting to Database Table " & Me.mainDatabase & "." & stRemoteTableName
DoEvents
On Error Resume Next
CurrentDb.TableDefs.Delete stLocalTableName
If Err.Number <> 0 Then
If Err.Number <> 3265 Then GoTo AttachTable_Err 'v4.0.44 - allow delete errors
Err.Clear
End If
On Error GoTo AttachTable_Err
Set td = CurrentDb.CreateTableDef(stLocalTableName, dbAttachSavePWD, stRemoteTableName, getStrConnDAO(configID))
CurrentDb.TableDefs.Append td
DoEvents
AttachTable = True
Exit Function
AttachTable_Err:
AttachTable = False
errMsg = "AttachTable encountered an unexpected error: " & Err.description & " on table " & stRemoteTableName & " in database " & Me.mainDatabase
End Function
You will need to getConStrDAO function
Private ADOconnStr As String
Private DAOconnStr As String
Public Function getStrConn(Optional configID As Long = 0) As String
'create a connection string for use when running stored procedures
'this uses the saved value if possible, but global variables are reset if an error occurs
If ADOconnStr = "" Then
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim account As String
Dim revealedPassword As String
Dim s As String, i As Integer, x As String
Set conn = CurrentProject.Connection
If configID = 0 Then configID = Nz(Form_frmLogon.configID, 0)
Set rs = conn.Execute("SELECT * FROM localConfig WHERE id =" & configID)
If Not rs.EOF Then
ADOconnStr = "Provider=Microsoft.Access.OLEDB.10.0;Data Provider=SQLOLEDB;SERVER=" 'this provider is needed to allow use of SP as form.recordset
ADOconnStr = ADOconnStr & rs("ServerName") & ";DATABASE=" & rs("DatabaseName") & ";UID="
ADOconnStr = ADOconnStr & rs("dbUser") & ";PWD=" & EncryptDecrypt(Nz(rs("dbPassword"), ""))
End If
rs.Close
Set rs = Nothing
Set conn = Nothing
End If
getStrConn = ADOconnStr
End Function
Public Sub resetConnection()
ADOconnStr = ""
DAOconnStr = ""
End Sub
Function getStrConnDAO(Optional configID As Long = 0) As String
If DAOconnStr = "" Then
Dim a As New ADODB.Connection
a.Open getStrConn(configID)
DAOconnStr = "ODBC;driver=SQL Server;" & a.Properties("Extended Properties") & ";"
Set a = Nothing
End If
getStrConnDAO = DAOconnStr
End Function
And finally a simple encryption of database password to make it not obvious to casual eyes - something again copied from the internet
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''' Comments: Performs XOr encryption/decryption on string data. Passing a
''' string through the procedure once encrypts it, passing it
''' through a second time decrypts it.
'''
''' Arguments: szData [in|out] A string containing the data to
''' encrypt or decrypt.
'''
''' Date Developer Action
''' --------------------------------------------------------------------------
''' 05/18/05 Rob Bovey Created
'''
Public Function EncryptDecrypt(szData As String) As String
Const lKEY_VALUE As Long = 215
Dim bytData() As Byte
Dim lCount As Long
bytData = szData
For lCount = LBound(bytData) To UBound(bytData)
bytData(lCount) = bytData(lCount) Xor lKEY_VALUE
Next lCount
EncryptDecrypt = bytData
End Function
I've written a console app in VB.NET to do some database work and a strange runtime error has arisen...
Here's the main code:
Sub Main(ByVal args() As String)
Try
user = args(0)
batchID = args(1)
GetBatchRevision()
'batchRev = 1
Dim getTestScripts As SqlCommand = New SqlCommand("GetTestScriptsInTestBatch", cs)
getTestScripts.CommandType = CommandType.StoredProcedure
Dim batchIDParam As SqlParameter = getTestScripts.Parameters.Add("#batchID", SqlDbType.Int, 4)
Dim batchRevParam As SqlParameter = getTestScripts.Parameters.Add("#batchRev", SqlDbType.Int, 4)
'batchIDParam.Value = 1
'batchRevParam.Value = 1
batchIDParam.Value = batchID
batchRevParam.Value = batchRev
Console.WriteLine(batchID & " " & batchRev)
Console.WriteLine(cs.State)
Console.ReadLine()
Using cs
cs.Open()
Dim reader As SqlDataReader = getTestScripts.ExecuteReader(CommandBehavior.CloseConnection)
While reader.Read()
Console.WriteLine("Executing Test Script " & reader("ScriptID").ToString() & " Revision " & reader("ScriptRev").ToString)
End While
Console.ReadLine()
End Using
Catch ex As Exception
End Try
End Sub
GetBatchRevision:
Private Sub GetBatchRevision()
Using cs
Dim GetNewestRev As New SqlCommand("SELECT Max(BatchRev) FROM TestBatch WHERE BatchID=" & batchID, cs)
cs.Open()
Dim reader As SqlDataReader = GetNewestRev.ExecuteReader(CommandBehavior.CloseConnection)
reader.Read()
If Not IsDBNull(reader(0)) Then
batchRev = reader(0).ToString()
End If
End Using
End Sub
batchRev and batchID are both global variables within the module.
Behaviorally:
The app prints out "1" (user input), "1" (database result), "0" (enum of Closed connection)
When I press Enter to get past the first Console.ReadLine(), the app simply closes out.
If I comment out GetBatchRevision and directly set batchRev = 1, I get the above result as well as "Executing Test Script 1 Revision 52", "Executing Test Script 2 Revision 66" which are the expected results from the stored procedure GetTestScriptsInTestBatch.
The global variable declarations are as follows:
Private batchID As String
Private batchRev As String
Any ideas why GetBatchRevision() causes the app to crash? By itself (removing the stored proc part of the code), it executes just fine. My initial guess was that there was a hanging connection, but ending a "using" block is supposed to close a SQL connection as well as any open readers associated with said connection (as mentioned before, cs.State returns 0).
Your problem is on these lines:
reader.Read()
If Not IsDBNull(reader(0)) Then
reader.Read() is probably returning false; yet you try to access reader(0). Boom!
You should change it to:
IF reader.Read() AndAlso Not IsDBNull(reader(0)) Then
'' etc
End If
It looks like cs is also a global variable. This is a bad idea. .Net data access works a lot better when you're using a new connection each time. You're probably fine in this app, but you're setting up some bad habits. Instead, load your connection string as a global variable and use that when creating your connections.
Next up, there's no reason for GetBatchRevision() to talk to global variables. Have it accept an argument and return it's result instead. And of course I can't overlook the sql injection issue because you concatentate the batchid to the end of your string. Here's the new version of the function after fixing those errors:
Private Function GetBatchRevision(ByVal BatchID As String) As String
Using cn As New SqlConnection(cs), _
GetNewestRev As New SqlCommand("SELECT Max(BatchRev) FROM TestBatch WHERE BatchID= #BatchID", cn)
GetNewestRev.Parameters.Add("#Batch", SqlDbType.Int).Value = Convert.ToInt32(BatchId)
cn.Open()
Return GetNewestRev.ExecuteScalar().ToString()
End Using
End Function
This can get even better if you keep BatchRev and BatchID as int's rather than strings internally.
I am performing a simple exercise of opening an SQL Server database connection, pulling the first record of a table from a DataReader object, and then closing the object. However, I have noticed that theres bit of a delay, about 5 seconds or so, in closing the connection. However, the delay only occurs after the command object executes the specified query. I've worked in a setup like this before and don't remember there being such a long delay while closing the connection.
Public Sub TestDb()
Dim cnStrBuilder As New SqlClient.SqlConnectionStringBuilder
Dim cn As New SqlClient.SqlConnection
Dim sqlSelectName As New SqlClient.SqlCommand
Dim drName As SqlClient.SqlDataReader
Dim newName As New SymName
Dim i As Integer
cnStrBuilder.UserID = "sa"
cnStrBuilder.ConnectTimeout = 30
cnStrBuilder.Password = ""
cnStrBuilder.PersistSecurityInfo = True
cnStrBuilder.DataSource = "EMARKET\FL_DB"
cnStrBuilder.InitialCatalog = "EmailMarketing"
sqlSelectName.CommandType = CommandType.Text
sqlSelectName.CommandText = "SELECT * FROM [NAME]"
System.Console.WriteLine(cnStrBuilder.ConnectionString)
cn.ConnectionString = cnStrBuilder.ConnectionString
Try
If cn.State = ConnectionState.Closed Then
cn.Open()
End If
System.Console.WriteLine("Connection success")
sqlSelectName.Connection = cn
System.Console.WriteLine("Execute Reader")
drName = sqlSelectName.ExecuteReader
If drName.HasRows = True Then
System.Console.WriteLine("Read Row")
drName.Read()
For i = 0 To drName.FieldCount - 1
Console.WriteLine(drName.Item(i).ToString)
Next
End If
System.Console.WriteLine("Closing connection")
sqlSelectName.Connection.Close()
Catch ex As Exception
System.Console.WriteLine("Something Happened")
System.Console.WriteLine(ex.Message)
End Try
System.Console.WriteLine("Done.")
End Sub
If I omit the lines
'System.Console.WriteLine("Execute Reader")
'drName = sqlSelectName.ExecuteReader
'
'If drName.HasRows = True Then
' System.Console.WriteLine("Read Row")
' drName.Read()
'
' For i = 0 To drName.FieldCount - 1
' Console.WriteLine(drName.Item(i).ToString)
' Next
'End If
The connection closes almost imediately. What gives? I have narrowed it down to the where the .ExecuteReader line that causes the delay in the connection close. Whats causing the delay and how do I resolve it?
You're telling SQL Server to retrieve the entire table. Yet after the first row, you stop, and close the connection. Like you, I would expect the connection to close immediately, but perhaps the server is busy spooling the table to a place where it can return your data quickly.
Does the connection still close slowly if you only ask the server for one row? F.e.
sqlSelectName.CommandText = "SELECT TOP 1 * FROM [NAME]"
Generally, you should be wrapping any objects that implement IDisposable in using statements which includes the connection object. I would try implementing something like the following which is from MSDN:
http://msdn.microsoft.com/en-us/library/y6wy5a0f.aspx#Y400
Public Sub CreateCommand(ByVal queryString As String, _
ByVal connectionString As String)
Using connection As New SqlConnection(connectionString)
Dim command As New SqlCommand(queryString, connection)
connection.Open()
Dim reader As SqlDataReader = _
command.ExecuteReader(CommandBehavior.CloseConnection)
While reader.Read()
Console.WriteLine("{0}", reader(0))
End While
End Using
End Sub
I have recently started encountering Database connection issues with SQL Server on my development machine.
System.InvalidOperationException: Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool
How can I monitor the connection pool to figure out what is happening?
Further Info:
I haven't had much luck with this - I'm definitely not leaking connections. Every connection is inside a using statement.
When the problem does occur, I have the Performance Monitor window open and it's not showing anywhere near the limit of the pool (which is 100) - generally around 2 - 5 connections, so I don't think the pool is being exhausted so maybe it's a timeout.
However, I have set the ConnectionTimeout to 0 - which, according to the documentation, means it should wait forever to connect - but I'm not seeing this.
When it does occur, it happens fairly quickly - I'm running under the debugger from VS2010 - starting a new instance of my application - and it might happen within a second or two of starting - in starting up the app there are several queries that happen. The actual SQL Server I'm running against is SQL Express 2008. Maybe I should try running it against SQL Server 2008 and see if I see any different behaviour.
Any other ideas?
Take a look at the ADO.NET Performance Counters related to pooling.
Your described symptom is often an indication that you are leaking connections. Make sure all connections are disposed when you are finished with them, preferably by wrapping in an using statement.
here's some code to try the pool and then failover to unpooled:
use this sub if a problem happens with the pool:
Public Sub OpenConn()
Dim sTempCNString As String = cn.ConnectionString
Try
' add a timeout to the cn string, following http://www.15seconds.com/issue/040830.htm
Dim iTimeOut As Integer = utils_Configuration.Get_ConfigInt("DBConnectTimeout", 0)
If (iTimeOut > 0 And Not cn.ConnectionString.ToLower.Contains("timeout")) Then
Diagnostics.Debug.Print("<><><><><><><> SHORT CONNECT WITH POOLING <><><><><><><><><> ")
cn.ConnectionString += ";Connect Timeout=" & iTimeOut.ToString() & ";"
End If
cn.Open()
IsOperational = True
Catch ex As Exception
Diagnostics.Debug.Print("ERROR IN OPENING, try no pool")
' see http://www.15seconds.com/issue/040830.htm
' turn off pooling
Diagnostics.Debug.Print("<><><><><><><> CONNECT WITHOUT POOLING <><><><><><><><><> ")
Dim sAddOn As String = ";Pooling=false;Connect Timeout=45;"
cn.ConnectionString = sTempCNString & sAddOn
cn.ConnectionString = cn.ConnectionString.Replace(";;", ";")
cn.Open()
End Try
End Sub
Here's some code to monitor the pool:
Option Explicit On
Option Strict On
Imports System.Data.SqlClient
Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports Microsoft.VisualBasic
' ref: http://msdn2.microsoft.com/en-us/library/ms254503.aspx
Public Class utils_SqlPerfMon
Private PerfCounters(9) As PerformanceCounter
Private connection As SqlConnection
Public sConnectString As String = ""
Public sResult As String = ""
Public Sub New()
sConnectString = Tools.GetMainDBConn().ConnectionString
connection = New SqlConnection(sConnectString)
Exec()
End Sub
Public Sub New(ByVal strC As String)
sConnectString = strC
connection = New SqlConnection(sConnectString)
Exec()
End Sub
Public Sub Exec()
Me.SetUpPerformanceCounters()
Diagnostics.Debug.Print("Available Performance Counters:")
' Create the connections and display the results.
Me.CreateConnectionsAndDisplayResults()
End Sub
Private Sub CreateConnectionsAndDisplayResults()
' List the Performance counters.
WritePerformanceCounters()
Dim connection1 As SqlConnection = New SqlConnection( _
Me.sConnectString)
connection1.Open()
Diagnostics.Debug.Print("Opened the 1st Connection:")
WritePerformanceCounters()
connection1.Close()
Diagnostics.Debug.Print("Closed the 1st Connection:")
WritePerformanceCounters()
Return
End Sub
Private Enum ADO_Net_Performance_Counters
NumberOfActiveConnectionPools
NumberOfReclaimedConnections
HardConnectsPerSecond
HardDisconnectsPerSecond
NumberOfActiveConnectionPoolGroups
NumberOfInactiveConnectionPoolGroups
NumberOfInactiveConnectionPools
NumberOfNonPooledConnections
NumberOfPooledConnections
NumberOfStasisConnections
' The following performance counters are more expensive to track.
' Enable ConnectionPoolPerformanceCounterDetail in your config file.
' SoftConnectsPerSecond
' SoftDisconnectsPerSecond
' NumberOfActiveConnections
' NumberOfFreeConnections
End Enum
Private Sub SetUpPerformanceCounters()
connection.Close()
Me.PerfCounters(9) = New PerformanceCounter()
Dim instanceName As String = GetInstanceName()
Dim apc As Type = GetType(ADO_Net_Performance_Counters)
Dim i As Integer = 0
Dim s As String = ""
For Each s In [Enum].GetNames(apc)
Me.PerfCounters(i) = New PerformanceCounter()
Me.PerfCounters(i).CategoryName = ".NET Data Provider for SqlServer"
Me.PerfCounters(i).CounterName = s
Me.PerfCounters(i).InstanceName = instanceName
i = (i + 1)
Next
End Sub
Private Declare Function GetCurrentProcessId Lib "kernel32.dll" () As Integer
Private Function GetInstanceName() As String
'This works for Winforms apps.
'Dim instanceName As String = _
' System.Reflection.Assembly.GetEntryAssembly.GetName.Name
' Must replace special characters like (, ), #, /, \\
Dim instanceName As String = _
AppDomain.CurrentDomain.FriendlyName.ToString.Replace("(", "[") _
.Replace(")", "]").Replace("#", "_").Replace("/", "_").Replace("\\", "_")
'For ASP.NET applications your instanceName will be your CurrentDomain's
'FriendlyName. Replace the line above that sets the instanceName with this:
'instanceName = AppDomain.CurrentDomain.FriendlyName.ToString.Replace("(", "[") _
' .Replace(")", "]").Replace("#", "_").Replace("/", "_").Replace("\\", "_")
Dim pid As String = GetCurrentProcessId.ToString
instanceName = (instanceName + ("[" & (pid & "]")))
Diagnostics.Debug.Print("Instance Name: {0}", instanceName)
Diagnostics.Debug.Print("---------------------------")
Return instanceName
End Function
Private Sub WritePerformanceCounters()
Dim sdelim As String = vbCrLf ' "<br>"
Diagnostics.Debug.Print("---------------------------")
sResult += "---------------------------"
sResult += sdelim
Dim strTemp As String = ""
For Each p As PerformanceCounter In Me.PerfCounters
Try
Diagnostics.Debug.Print("{0} = {1}", p.CounterName, p.NextValue)
strTemp = p.CounterName & "=" & p.NextValue.ToString
Catch ex As Exception
strTemp = ""
End Try
sResult += strTemp
sResult += sdelim
Next
Diagnostics.Debug.Print("---------------------------")
sResult += "---------------------------"
sResult += sdelim
End Sub
Private Shared Function GetSqlConnectionStringDifferent() As String
' To avoid storing the connection string in your code,
' you can retrive it from a configuration file.
Return ("Initial Catalog=AdventureWorks;Data Source=.\SqlExpress;" & _
"User Id=LowPriv;Password=Data!05;")
End Function
End Class