vb.net polling Sql server once a second causing timeout - sql

I was asked to do a system that would poll one table in the database every second and if it counters a row that meets a criteria start actions to handle that.
I've done this but every now and then I get a time out exception. I have a WPF application where I have a thread that runs in background. This thread has a loop and sleeps for one second at the end of the loop. The connection to the database is opened inside "using" clause.
Below is my thread sub:
Private Sub PollDatabase()
While m_StopThread = False
Try
Dim listOfRows As List(Of DataObject) = db.GetDataObjects()
... Do something with the rows ...
Catch ex As Exception
m_log.WriteLine(ex.ToString())
End Try
Thread.Sleep(1000)
End While
End Sub
And my SQL function looks like this:
Public Function GetDataObjects() As List(Of DataObject)
Dim result As New List(Of DataObject)
Dim sb As New StringBuilder("... the sql query ...")
Using cnn = New SqlConnection(_connectionString)
cnn.Open()
Using cmd = New SqlCommand(sb.ToString(), cnn)
cmd.CommandTimeout = 0
Using DataReader As SqlDataReader = cmd.ExecuteReader()
Do While DataReader.Read()
... read the columns from table
to the dataobject ...
result.Add(DataObject)
Loop
End Using
End Using
End Using
Return result
End Function
Now what seems randomly my log has a time out exception:
System.Data.SqlClient.SqlException (0x80131904): Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
...
at System.Data.SqlClient.SqlConnection.Open()
My questions are: is this at all save way of doing this? Or am I doing something fundamentally wrong here? And of course if anyone have a suggestion to fix this issue.
EDIT:
I tried a bit different approach with the SQL function. I'm now opening a connection once when my application starts and dumped the "using" clauses. So my function looks something like this now:
Public Function GetDataObjects() As List(Of DataObject)
Dim result As New List(Of DataObject)
Dim sb As New StringBuilder("... the sql query ...")
_sqlCmd.CommandText = sb.ToString()
Using DataReader As SqlDataReader = _sqlCmd.ExecuteReader()
Do While DataReader.Read()
... fill the list with objects ...
Loop
End Using
Return result
End Function
My log is clean form errors. So is there something wrong opening a connection to the server once in a second as I do with the using?
EDIT:
I've done a lot of testing now to identify the problem. What I discovered is that just connecting multiple times to the server doesn't cause any problems. Neither does adding a select statement after the connection. But when I actually implement a function where is the complete reader part and return my results I run into the time out problems. Here is two examples.
This isn't causing issues:
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs)
Me.DataContext = Me
m_Thread = New Thread(AddressOf ConnectionTestFunction)
m_Thread.IsBackground = True
m_Thread.Start()
End Sub
Private Sub ConnectionTestFunction()
While m_stopThread = False
Try
m_log.WriteLine("GetData (" & m_ThreadCounter & ")")
Using cnn As SqlConnection = New SqlConnection("Data Source=server;Initial Catalog=db;Integrated Security=True;MultipleActiveResultSets=True")
cnn.Open()
Using cmd As SqlCommand = New SqlCommand("SELECT * FROM Data", cnn)
Using DataReader As SqlDataReader = cmd.ExecuteReader()
Do While DataReader.Read()
Loop
End Using
End Using
End Using
Catch ex As Exception
m_log.WriteLine(ex.ToString())
End Try
m_ThreadCounter += 1
Thread.Sleep(1000)
End While
End Sub
This is causing timeout errors:
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs)
Me.DataContext = Me
m_Thread = New Thread(AddressOf ConnectionTestFunction)
m_Thread.IsBackground = True
m_Thread.Start()
End Sub
Private Sub ConnectionTestFunction()
While m_stopThread = False
Try
m_log.WriteLine("GetData (" & m_ThreadCounter & ")")
Dim datarows As List(Of Data) = Me.GetData()
Catch ex As Exception
m_log.WriteLine(ex.ToString())
End Try
m_ThreadCounter += 1
Thread.Sleep(1000)
End While
End Sub
Private Function GetData() As List(Of Data)
Dim result As New List(Of Data)
Using cnn As SqlConnection = New SqlConnection("Data Source=server;Initial Catalog=db;Integrated Security=True;MultipleActiveResultSets=True")
cnn.Open()
Using cmd As SqlCommand = New SqlCommand("SELECT * FROM Data", cnn)
Using DataReader As SqlDataReader = cmd.ExecuteReader()
Do While DataReader.Read()
Dim d As New Data()
d.DataId = DataReader("DataId")
... etc fields about 10 of them ...
result.Add(d)
Loop
End Using
End Using
End Using
Return result
End Function
I'm really happy if anyone have any thoughts about this... I have to admit I'm really confused now.

Probably, your code is taking longer to complete than the default time-out value for the connection is. Try specifying the time-out when you create your Sql Connection. Make sure that it's longer than the time your code needs to complete.

This approach doesn't seem very good.. Instead of pooling, why not react when new data comes? You can use a trigger or SqlDependency?
http://dotnet.dzone.com/articles/c-sqldependency-monitoring

Related

"No value given for one or more required parameters" error using OleDbCommand

I am trying to update a record in MS Access using VB.net. This code will be under the "Delivered" button. When I try to run it, it shows the "No value given for one or more required parameters" error. Here is my code:
Private Const strConn As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\Traicy\Downloads\MWL(11-30-2021)\MadeWithLove\MadeWithLove\MadeWithLove.mdb;"
ReadOnly conn As OleDbConnection = New OleDbConnection(strConn)
Dim cmd As OleDbCommand
Public Sub DeliveredUpdate()
Const SQL As String = "UPDATE DELIVERY SET delivery_status = #status"
cmd = New OleDbCommand(SQL, conn)
' Update parameter
cmd.Parameters.AddWithValue("#status", "Delivered")
' Open connection, update, then close connection
Try
conn.Open()
If cmd.ExecuteNonQuery() > 0 Then
MsgBox("The delivery status was successfully updated.")
End If
conn.Close()
Catch ex As Exception
MsgBox(ex.Message)
conn.Close()
End Try
End Sub
Do not declare connections or commands outside of the method where they are used. These database objects use unmanaged resources. They release these resources in their Dispose methods. The language provides Using blocks to handle this.
As mentioned in comments by Andrew Morton, you should have a Where clause to tell the database which record to update. This would contain the primary key of the record. I guessed at a name for the field, OrderID. Check your database for the real field name.
Access does not use named parameters but you can use names for readability. Access will be able to recognize the parameters as long as they are added to the Parameters collection in the same order that they appear in the sql string. In some databases the Add method is superior to AddWithValue because it doesn't leave the datatype to chance.
It is a good idea to separate your database code from your user interface code. If you want to show a message box in your Catch put the Try blocks in the UI code. This way your function can be used in a web app or mobile app without rewriting.
Public Function DeliveredUpdate(ID As Integer) As Integer
Dim recordsUpdated As Integer
Dim SQL As String = "UPDATE DELIVERY SET delivery_status = #status Where OrderID = #Id;"
Using conn As New OleDbConnection(strConn),
cmd As New OleDbCommand(SQL, conn)
cmd.Parameters.Add("#status", OleDbType.VarChar).Value = "Delivered"
cmd.Parameters.Add("#Id", OleDbType.Integer).Value = ID
conn.Open()
recordsUpdated = cmd.ExecuteNonQuery
End Using 'closes and disposes the command and connection
Return recordsUpdated
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim retVal As Integer
Dim id As Integer = 1 'not sure where you are getting this value from
Try
retVal = DeliveredUpdate(id)
Catch ex As Exception
MsgBox(ex.Message)
End Try
If retVal > 0 Then
MsgBox("The delivery status was successfully updated.")
End If
End Sub

VB.NET How to correctly loop through a result set

I have looked at many different code snippets on this site looking that would show me how to do something that should be fairly simple once I have the knowledge.
I want to query a database table for an array of values and then populate a combobox with those results.
Here is what I have so far:
Public Sub getMachines()
Try
Dim SQL As String = "SELECT MachineName from machine"
Form1.machineName.DisplayMember = "Text"
Dim tb As New DataTable
tb.Columns.Add("Text", GetType(String))
Using cn As New MySqlConnection(ConnectionString)
Using cmd As New MySqlCommand(SQL, cn)
For Each cmd As String In cmd
'I want to add each value found in the database to "tb.Rows.Add"
'tb.Rows.Add(???)
Next
Form1.machineName.DataSource = tb
cn.Open()
cmd.ExecuteNonQuery()
End Using
cn.Close()
End Using
Catch ex As MySqlException
MsgBox(ex.Message)
End Try
End Sub
I proceeded much like you did. I used the Load method of the DataTable. It is not necessary to set the column name and type. The name of the column is taken from the Select statement and the datatype is inferred by ADO.net from the first few records.
Luckily a DataTable can be an Enumerable using the .AsEnumnerable method. Then we can use Linq to get all the values from the MachineName column. Calling .ToArray causes the Linq to execute. If you hold your cursor over names on this line you will see that the datatype is String(). Just what we need to fill a combo box.
Code for a class called DataAccess
Private ConnectionString As String = "Your Connection String"
Public Function GetMachineNames() As String()
Dim tb As New DataTable
Dim SQL As String = "SELECT MachineName from machine;"
Using cn As New MySqlConnection(ConnectionString)
Using cmd As New MySqlCommand(SQL, cn)
cn.Open()
dt.Load(cmd.ExecuteReader)
End Using
End Using
Dim names = dt.AsEnumerable().Select(Function(x) x.Field(Of String)("MachineName")).ToArray()
Return names
End Function
In the form load you combo box like this.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim DatAcc As New DataAccess()
Dim arr = DatAcc.GetMachineNames()
machineName.DataSource = arr
End Sub
If you just want the MachineName to be displayed in the ComboBox, then just use that as the DisplayMember; don't bother creating another column called Text.
Public Sub getMachines()
Try
Dim cmd As String = "SELECT MachineName from machine"
Dim ds As New DataSet()
Using con As New MySqlConnection(ConnectionString)
Using da As New MySqlDataAdapter(cmd, con)
da.Fill(ds)
With Form1.machineName
.DisplayMember = "MachineName"
.ValueMember = "MachineName"
.DataSource = ds
End With
End Using
End Using
Catch ex As MySqlException
MsgBox(ex.Message)
End Try
End Sub
I'll show a few examples, including using parameters, since that is important.
First up, a quick translation to run the existing query and loop through the results:
Public Sub getMachines()
Try
Dim SQL As String = "SELECT MachineName from machine"
Using cn As New MySqlConnection(ConnectionString), _
cmd As New MySqlCommand(SQL, cn)
cn.Open()
Using rdr As MySqlDatareader = cmd.ExecuteReader
While rdr.Read()
Form1.machineName.Items.Add(rdr("MachineName"))
End While
End Using
End Using
Catch ex As MySqlException
MsgBox(ex.Message)
End Try
End Sub
But better practice for a method like this is to isolate data access for the UI. This method should return results to the caller, which can decide what do with them. So I'll show two methods: one to get the data, and the other to loop through it and set up the combobox:
Private Function GetMachines() As DataTable
'No try/catch needed here. Handle it in the UI level, instead
Dim SQL As String = "SELECT MachineName from machine"
Dim result As New DataTable
Using cn As New MySqlConnection(ConnectionString), _
cmd As New MySqlCommand(SQL, cn),
da As New MySqlDataAdapter(cmd)
da.Fill(result)
End Using
Return result
End Function
Public Sub LoadMachines()
Try
For Each item As DataRow in getMachines().Rows
Form1.machineName.Items.Add(item("MachineName"))
Next
Catch ex As MySqlException
MsgBox(ex.Message)
End Try
End Sub
Or, we can use DataBinding:
Private Function GetMachines() As DataTable
Dim SQL As String = "SELECT MachineName from machine"
Dim result As New DataTable
Using cn As New MySqlConnection(ConnectionString), _
cmd As New MySqlCommand(SQL, cn),
da As New MySqlDataAdapter(cmd)
da.Fill(result)
End Using
Return result
End Function
Public Sub LoadMachines()
Try
Form1.machineName.DisplayMember = "FirstName";
Form1.machineName.ValueMember = "City"
Form1.machineName.DataSource = GetMachines()
Catch ex As MySqlException
MsgBox(ex.Message)
End Try
End Sub
If you ever want to use a filter, you might do this (notice the overloading):
Private Function GetMachines(ByVal machineFilter As String) As DataTable
Dim SQL As String = "SELECT MachineName from machine WHERE MachineName LIKE #Filter"
Dim result As New DataTable
Using cn As New MySqlConnection(ConnectionString), _
cmd As New MySqlCommand(SQL, cn),
da As New MySqlDataAdapter(cmd)
'Match the MySqlDbType to your actual database column type and length
cmd.Parameters.Add("#Filter", MySqlDbType.VarString, 30).Value = machineFilter
da.Fill(result)
End Using
Return result
End Function
Private Function GetMachines(ByVal machineFilter As String) As DataTable
Return GetMachines("%")
End Function
Query parameters like that are very important, and if you were doing string concatenation to accomplish this kind of thing on your old platform, you were doing very bad things there, too.
Finally, let's get fancy. A lot of the time, you really don't want to load an entire result set into RAM, as is done with a DataTable. That can be bad. Instead, you'd like be able to stream results into memory and only work with one at a time, minimizing RAM use. In these cases, you get to play with a DataReader... but returning a DataReader object from within a Using block (which is important) doesn't work that well. To get around this, we can use functional programming concepts and advanced language features:
Private Iterator Function GetMachines(ByVal machineFilter As String) As IEnumerable(Of String)
Dim SQL As String = "SELECT MachineName from machine WHERE MachineName LIKE #Filter"
Using cn As New MySqlConnection(ConnectionString), _
cmd As New MySqlCommand(SQL, cn)
'Match the MySqlDbType to your actual database column type and length
cmd.Parameters.Add("#Filter", MySqlDbType.VarString, 30).Value = machineFilter
cn.Open()
Using rdr As MySqlDatareader = cmd.ExecuteReader
While rdr.Read()
Dim result As String = rdr("MachineName")
Yield Return result
End While
End Using
End Using
Return result
End Function
Private Function GetMachines() As IEnumerable(Of String)
Return GetMachines("%")
End Function

Filling DataTable as source of DataGridView is very slow

So, here is a case where I have more than 40.000 rows in my database.
When I start Select * from Table it gets me result set in less than a second.
But, when I want to fill DataTable with those rows and than show it in DataGridView it takes forever to show (Around 15-20 seconds).
Why is that?
My code:
Public Shared Function FillDTwithSQL(ByVal StoredProc As String, ByVal table As DataTable) As DataTable
Dim cmd As SqlCommand = CreateCommand(SProcedura)
Dim dt As New DataTable("dt")
dt = table
dt.Rows.Clear()
Try
If adoConnection.State = ConnectionState.Closed Then adoConnection.Open()
dt.Load(cmd.ExecuteReader(CommandBehavior.Default))
Catch ex As Exception
MsgBox("Greska: " & ex.ToString)
Error = True
Error_text = ex.ToString
End Try
adoConnection.Close()
Return dt
End Function
Public Shared Function CreateCommand(ByVal SProcedura As String) As SqlCommand
CreateConnection(ConnectionSetup.ConnectionString)
Dim cmd As New SqlCommand(ConnectionSetup.DataBaseName & ".dbo." & SProcedura, adoConnection)
cmd.CommandType = CommandType.StoredProcedure
Return cmd
End Function
Private sub FillDGV()
DataBaseLayer.FillDTwithSQL("SelectProc", ds_Tables.Table)
Me.DataGridView1.DataSource = dds_Tables.Table
Me.DataGridView1.ClearSelection()
End Sub
For large amounts of data it is recommended to use the DataGridView's virtual mode.
Have a look at this link: How to: Implement Virtual Mode in the Windows Forms DataGridView Control
To summarize the link: You have to implement your own data caching by implementing the DataGridView's CellValueNeeded event and set Me.DataGridView1.VirtualMode = true.

vb.net and using SQLite

I just want to make sure that this is the right code for working with SQLite for fastest performance..
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim cons As New SQLite.SQLiteConnection
cons.ConnectionString = "Data Source=C:\database.s3db; Version=3"
cons.Open()
Using tx = cons.BeginTransaction()
Dim cmd As SQLite.SQLiteCommand
cmd = cons.CreateCommand()
For i As Integer = 0 To 1000
Try
cmd.CommandText = "INSERT INTO table1 VALUES('" & i & "')"
cmd.ExecuteNonQuery()
Catch ex As Exception
End Try
Next
tx.Commit()
cmd.Dispose()
End Using
cons.Close()
End Sub
As I mentioned, proper way of doing this using parameterized queries, not swallowing the exception, and using statements wherever necessary would look something like this.
Do note this is not the fastest way of doing.
Private Sub InsertRows()
Using conn As New SqlConnection
conn.Open()
Using tx = conn.BeginTransaction()
For i As Integer = 0 To 1000
Using cmd = conn.CreateCommand() 'Proper using statements wherever necessary
Try
cmd.CommandText = "INSERT INTO table1 VALUES(#ColumnName)" 'Paramertized queries
cmd.Parameters.AddWithValue("#ColumnName", i)
cmd.ExecuteNonQuery()
Catch ex As Exception
logger.ErrorException(ex) 'Logging the exception or shoe messagebox
End Try
End Using
Next
End Using
End Using
End Sub
Ideally you shouldn't continue execution if there is an exception in a tight loop doing same task. If one fails there is a chance that everything can fail. So in that case you should remove the try/catch inside the loop and wrap it over the for loop.

Better way to print out rows from a datatable in vb.net

I am new to vb.net and I am trying to query a database and print out the records in the row to the console window. I got it to work, but I have a feeling that there is a more concise way to do this. One thing that I am sure is wrong is that I had to convert the dataset to a datatable to be able to retrieve the values. Is that correct? Could you take a look at the code below (especially the for loop) and let me know what I can improve upon?
Thanks!
Module Module1
Sub Main()
Dim constring As String = "Data Source=C:\Users\test\Desktop\MyDatabase1.sdf"
Dim conn As New SqlCeConnection(constring)
Dim cmd As New SqlCeCommand("SELECT * FROM ACCOUNT")
Dim adapter As New SqlCeDataAdapter
Dim ds As New DataSet()
Try
conn.Open()
cmd.Connection = conn
adapter.SelectCommand = cmd
adapter.Fill(ds, "testds")
cmd.Dispose()
adapter.Dispose()
conn.Close()
Dim dt As DataTable = ds.Tables.Item("testds")
Dim row As DataRow
Dim count As Integer = dt.Columns.Count()
For Each row In dt.Rows
Dim i As Integer = 0
While i <= count - 1
Console.Write(row(i))
i += 1
End While
Console.WriteLine(Environment.NewLine())
Next
Catch ex As Exception
Console.WriteLine("There was an error")
Console.WriteLine(ex)
End Try
Console.ReadLine()
End Sub
End Module
Here is how I would rewrite this for a few reasons:
1) You should always use Using statements with disposable objects to ensure they are correctly cleaned up. You had a good start with the dispose commands, but this way is safer.
2) It is more efficient to use ExecuteReader than loading everything into a dataset.
3) Your try/catch statement should include object creation as well as execution.
Finally, in response to your question about datasets and datatables, that code was absolutely correct: a dataset consists of zero or more datatables, so you were just extracting the existing datatable from the dataset.
Try
Dim constring As String = "Data Source=C:\Users\test\Desktop\MyDatabase1.sdf"
Using conn As New SqlCeConnection(constring)
conn.Open()
Using cmd As New SqlCeCommand("SELECT * FROM ACCOUNT", conn)
Dim reader As SqlCeDataReader
reader = cmd.ExecuteReader()
Do While reader.Read
For i As Integer = 0 To reader.FieldCount - 1
Console.Write(reader.GetString(i))
Next
Console.WriteLine(Environment.NewLine())
Loop
End Using
End Using
Catch ex As Exception
Console.WriteLine("There was an error")
Console.WriteLine(ex)
End Try
Console.ReadLine()
End Sub
One last note: since you are just printing to the console, it doesn't matter as much, but whenever you deal with a lot of strings, especially those that are to be concatenated, you should always consider using System.Text.StringBuilder.
Here is an example rewrite of the loop that prints to the console using stringbuilder (builds the string in memory, then dumps it to the console; I have also added the field name for good measure):
Dim sbOutput As New System.Text.StringBuilder(500)
For i As Integer = 0 To reader.FieldCount - 1
If sbOutput.Length <> 0 Then
sbOutput.Append("; ")
End If
sbOutput.Append(reader.GetName(i)).Append("=").Append(reader.GetString(i))
Next
sbOutput.AppendLine()
Console.Write(sbOutput.ToString)