I have main windows form on which i have user control. From there I do some operation related to database. During database process I show user additional windows form so he can see nice circural graphic in meantime. I use task to place mentioned database process and from there if error occurs I want to show error message from try/catch. Unfortunetly MessageBox.Show is not showing up when i do tests - catch is even not reached. Could you help me out what is wrong. I am talking about this line in catch statment:
Catch sqlex As Exception
pic.Invoke(Sub() MessageBox.Show(pic, sqlex.Message))
Here's circural window's form code:
Public Class FrmCircularProgress
Sub New(progressType As DevComponents.DotNetBar.eCircularProgressType)
InitializeComponent()
CircularProgress1.ProgressBarType = progressType
StartCircular()
End Sub
Public Sub StartCircular()
Me.CircularProgress1.IsRunning = True
End Sub
Public Sub StopCircular()
Me.CircularProgress1.IsRunning = False
End Sub
End Class
This is example how i use it with Task:
Dim createArticle As New Artikel
Dim pic As New FrmCircularProgress(eCircularProgressType.Donut)
Dim tsk As Task(Of Boolean) = Task.Factory.StartNew(Of Boolean)(Function()
'--Run lenghty task
Dim resu = False
Try
resu = createArticle.ProcessArticle(_artikelsAndTheirVariationsFinal)
'--Close form once done (on GUI thread)
Catch sqlex As Exception
pic.Invoke(Sub() MessageBox.Show(pic, sqlex.Message))
Finally
pic.Invoke(New Action(Sub() pic.StopCircular()))
pic.Invoke(New Action(Sub() pic.Close()))
End Try
Return resu
End Function)
'--Show the form
pic.ShowDialog()
Task.WaitAll(tsk)
...
And just for you to see example database process in our case ProcessArticle which is returning either true or false
Public Function ProcessArticle(artikel As ArticlesVariations) As Boolean
Dim result = True
Dim strcon = New AppSettingsReader().GetValue("ConnectionString", GetType(System.String)).ToString()
Using connection As New SqlConnection(strcon)
'-- Open generall connection for all the queries
connection.Open()
'-- Make the transaction.
Dim transaction As SqlTransaction
transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted)
Dim newArticleRowId As Integer = 0
Dim articleIndex As Integer = 0
Try
For Each kvp As KeyValuePair(Of Integer, Artikel) In artikel.collection
Dim ckey As Integer = kvp.Key
articleIndex = kvp.Key 'save article key
Dim data As Artikel = kvp.Value
'-- If given article contains images list (artikel_images is a list with pictures associated with article)
If Not IsNothing(artikel.collection(articleIndex).ArtikelImages) Then
For Each img In artikel.collection(articleIndex).ArtikelImages
'--Insert article's images if exists
Using cmd As New SqlCommand("INSERT INTO T_Article_Image (Path, FK_Artikel_ID, Position) VALUES (#Path, #FK_Artikel_ID, #Position)", connection)
cmd.CommandType = CommandType.Text
cmd.Connection = connection
cmd.Transaction = transaction
cmd.Parameters.AddWithValue("#Path", img.Path)
cmd.Parameters.AddWithValue("#FK_Artikel_ID", newArticleRowId)
cmd.Parameters.AddWithValue("#Position", img.Position)
cmd.ExecuteScalar()
End Using
Next
End If
'-- If given article contains articles variations list (artikel_variation_attributes is a list with variations associated with article)
If Not IsNothing(artikel.collection(articleIndex)._artikel_variation_attributes) Then
For Each var In artikel.collection(articleIndex)._artikel_variation_attributes
'--Insert article's images if exists
Using cmd As New SqlCommand("INSERT INTO T_Artikel_T_Variation (FK_Variation_VariationAttribute_ID, FK_Artikel_ID, Position) VALUES (#FK_Variation_VariationAttribute_ID, #FK_Artikel_ID, #Position)", connection)
cmd.CommandType = CommandType.Text
cmd.Connection = connection
cmd.Transaction = transaction
cmd.Parameters.AddWithValue("#FK_Variation_VariationAttribute_ID", New Variation_VariationAttribute(var.FkVariationId, var.FkVariationAttributeId).GetId())
cmd.Parameters.AddWithValue("#FK_Artikel_ID", newArticleRowId)
cmd.Parameters.AddWithValue("#Position", var.Position)
cmd.ExecuteScalar()
End Using
Next
End If
Next
transaction.Commit()
Catch ex As Exception
result = False
'-- Roll the transaction back.
Try
transaction.Rollback()
Catch ex2 As Exception
result = False
End Try
End Try
End Using
Return result
End Function
I would not expect your catch to be reached. In your ProcessArticle function you have another Try .. Catch If there is an error in the SQL operation, this is where it will be caught. You then return false! So the calling routine does not catch the error, because it has already been handled gracefully. If you want to catch the error in your calling routine, then you will need to Raise an error from your catch in the ProcessArticle function.
You could change your Try Catch in ProcessArticle to
Catch ex As Exception
result = False
'-- Roll the transaction back.
Try
transaction.Rollback()
Catch ex2 As Exception
Throw New Exception("Rollback failed after first exception.", ex2)
End Try
Throw New Exception("Rollback succeeded after exception.", ex)
End Try
Sorry it's some time since I did VB, but I think the syntax is correct. Using this, the calling routine should now catch the Exception (and you will have an Exception with an inner exception). You will need to examine the inner exception to find the cause.
Related
So, for some reason my TRY CATCH is not capturing the exception thrown by SQL server stored procedure.
Basically, I execute a procedure and it does what it needs or raises an error. This error needs to be captured and raised in the VB app.
What am I doing wrong? And yes, SQL does throw an error...
Public Function InsertDetails(ByVal dBillingDate As Date) As Boolean
Dim result As Boolean = False
Dim bReturn As Boolean = True
Try
CleanUp(ObjCmd)
ObjCmd = UpgradeHelpers.DB.AdoFactoryManager.GetFactory().CreateCommand()
ObjCmd.CommandText = "usp_InsAfterHours"
ObjCmd.CommandType = CommandType.StoredProcedure
ObjCmd.CommandTimeout = 0
ObjCmd.Connection = SQLServerDB
'Get the parameters from the sproc
ParametersHelper.DeriveParameters(ObjCmd, UpgradeHelpers.DB.AdoFactoryManager.GetFactory())
With ObjCmd.Parameters
ObjCmd.Parameters(1).Value = dBillingDate
End With
ObjCmd.ExecuteNonQueryAsync()
Do While ObjCmd.Connection.State = ConnectionState.Executing
LoopSet = ""
Loop
Catch ex As Exception
If (TypeOf ex Is OleDb.OleDbException) Then
RaiseError(DirectCast(ex, OleDb.OleDbException).ErrorCode, Me.GetType().Name & ".InsertDetails", ex, DirectCast(ex, OleDb.OleDbException).Errors, g.sDatabaseUserName)
Else
RaiseError(CInt(ex.HResult), Me.GetType().Name & ".InsertDetails", ex, , g.sDatabaseUserName)
End If
bReturn = False
Finally
CleanUp(ObjCmd)
result = bReturn
End Try
Return result
End Function
A few changes I'd recommend:
You aren't waiting for the function to complete:
await ObjCmd.ExecuteNonQueryAsync()
Then remove the loop that's doing the checking.
Instead of RaiseError, use the command to throw the Exception.
Throw new ApplicationException(Me.GetType().Name & ".InsertDetails", ex)
Your caller code can then use the ex.GetBaseException() to get information such as the list of errors, assuming the ex.GetBaseException().Type is an Ole Exception
Ideally, you want to use System.Data.SqlClient instead of System.Data.OleDb for objects connecting to SQL Server in the .NET Framework.
I have been a long time reader of questions and answers on this site and this is my first question.
I have a Datagridview I want to load from a MySQL database. I can get the code to work if the datagridview is contained in the form I am writing the code in. If I request try and load it onto another form it does not work
The top bit of code works and the latter bit does not.
Public Sub LoadGrid3()
Dim job As String = TextBox10.Text
MySql.AddParam("#job", job)
Try
MySql.ExecQuery("select * from imprint.jobs_commonfields where jobno = #job")
Catch ex As Exception
MsgBox(ex.ToString)
End Try
If MySql.RecordCount > 0 Then DataGridView1.DataSource = MySql.mysqlds.Tables(0)
End Sub
Public Sub LoadGrid3()
Dim job As String = TextBox10.Text
MySql.AddParam("#job", job)
Try
MySql.ExecQuery("select * from imprint.jobs_commonfields where jobno = #job")
Catch ex As Exception
MsgBox(ex.ToString)
End Try
If MySql.RecordCount > 0 Then Form3.job_detail.DataSource = MySql.mysqlds.Tables(0)
End Sub
I have this sub in my WinForm:
Public Function CheckSentToday(ByVal date1 As DateTime) As Boolean
Dim cmSql As New SqlCommand("Check_Today", cnSql)
Try
cmSql.CommandType = CommandType.StoredProcedure
cmSql.Parameters.AddWithValue("#date1", String.Format("{0:yyyy-MM-dd}", date1))
If Not cnSql Is Nothing AndAlso cnSql.State = ConnectionState.Closed Then cnSql.Open()
If cmSql.ExecuteScalar IsNot Nothing Then
Return True
Else
Return False
End If
Catch ex As Exception
WriteToText("CheckSentToday", ex.ToString)
CheckSentToday = False
Finally
If Not cnSql Is Nothing AndAlso cnSql.State = ConnectionState.Open Then cnSql.Close()
End Try
End Function
I am opening the connection before executing the SqlCommand,
and closing the connection in the finally clause
Still, it is returning the following error everytime this sub is called:
Description:System.InvalidOperationException: Invalid attempt to call Read when reader is closed.
at System.Data.SqlClient.SqlDataReader.ReadInternal(Boolean setTimeout)
at System.Data.SqlClient.SqlDataReader.Read()
at System.Data.SqlClient.SqlCommand.CompleteExecuteScalar(SqlDataReader ds, Boolean returnSqlValue)
at System.Data.SqlClient.SqlCommand.ExecuteScalar()
Can Anyone help me figure out why?
Any help would be appreciated
Use the Using-statement instead and don't reuse the SqlConnection:
Public Function CheckSentToday(ByVal date1 As DateTime) As Boolean
Using cnSql = New SqlConnection("connection-string")
Using cmSql As New SqlCommand("Check_Today", cnSql)
cmSql.CommandType = CommandType.StoredProcedure
cmSql.Parameters.AddWithValue("#date1", String.Format("{0:yyyy-MM-dd}", date1))
Try
cnSql.Open()
If cmSql.ExecuteScalar IsNot Nothing Then
Return True
Else
Return False
End If
Catch ex As Exception
WriteToText("CheckSentToday: ", ex.ToString)
CheckSentToday = False
End Try
End Using
End Using
End Function
I'm trying to implement an asynchronous search "engine", but i'm facing some difficulties.
For some reason a SqlException is thrown every once in a while stating that:
"The variable name '#input' has already been declared. Variable names must be unique within a query batch or stored procedure."
Sample application
The following code targets the sys.messages table, so all you have to do is change the connection string.
Public Class Form1
Public Sub New()
Me.InitializeComponent()
Me.input = New TextBox() With {.Dock = DockStyle.Top, .TabIndex = 0}
Me.output = New RichTextBox() With {.Dock = DockStyle.Fill, .TabIndex = 1, .ReadOnly = True, .WordWrap = False}
Me.Controls.AddRange({Me.output, Me.input})
End Sub
Private Sub Search(sender As Object, e As EventArgs) Handles input.TextChanged
Dim input As String = Me.input.Text
Static command As SqlCommand
Static source As CancellationTokenSource
If (Not command Is Nothing) Then command.Cancel()
If (Not source Is Nothing) Then source.Cancel()
command = New SqlCommand()
source = New CancellationTokenSource()
Task.Factory.StartNew(Sub() Me.SearchingAsync(input, command, source.Token))
End Sub
Private Sub SearchingAsync(input As String, command As SqlCommand, token As CancellationToken)
Dim [error] As Exception = Nothing
Dim cancelled As Boolean = False
Dim result As List(Of sys_message) = Nothing
Try
Using connection As New SqlConnection("Server=instance\name;Database=name;Trusted_Connection=True;")
connection.Open()
command.Connection = connection
command.CommandType = CommandType.Text
command.CommandText = "select * from sys.messages where [text] like '%' + #input + '%';"
command.Parameters.AddWithValue("#input", input)
Using reader As SqlDataReader = command.ExecuteReader()
result = New List(Of sys_message)()
Do While (reader.Read() AndAlso (Not token.IsCancellationRequested))
result.Add(New sys_message() With {
.message_id = CInt(reader.Item("message_id")),
.language_id = CInt(reader.Item("language_id")),
.severity = CInt(reader.Item("severity")),
.is_event_logged = CBool(reader.Item("is_event_logged")),
.text = CStr(reader.Item("text"))
})
Loop
End Using
End Using
cancelled = token.IsCancellationRequested
Catch ex As SqlException When ex.Message.ToLower().Contains("operation cancelled by user")
cancelled = True
Catch ex As ThreadAbortException
cancelled = True
Catch ex As OperationCanceledException
cancelled = True
Catch ex As Exception
[error] = ex
Finally
Me.Invoke(
Sub()
'If (String.CompareOrdinal(input, Me.input.Text) = 0) Then
If (Not [error] Is Nothing) Then
Me.output.Text = String.Concat("Input='", input, "', Output={Result: 'error', Type: '", [error].GetType.Name, "', Message: '", [error].Message.Replace(Environment.NewLine, " "), "'}", Environment.NewLine, Me.output.Text).Trim()
ElseIf (cancelled) Then
Me.output.Text = String.Concat("Input='", input, "', Output={Result: 'cancelled'}", Environment.NewLine, Me.output.Text).Trim()
Else
Me.output.Text = String.Concat("Input='", input, "', Output={Result: 'success', Count: ", result.Count, "}", Environment.NewLine, Me.output.Text).Trim()
End If
'End If
End Sub
)
End Try
End Sub
Private WithEvents input As TextBox
Private WithEvents output As RichTextBox
Private Class sys_message
Public message_id As Integer
Public language_id As Integer
Public severity As Integer
Public is_event_logged As Boolean
Public text As String
End Class
End Class
Because you're inadvertently sharing SqlCommand objects between multiple calls to SearchingAsync. I'd remove the outer code that attempts to deal with trying to cancel those and just let SearchingAsync create its own, unshared instances.
At the same time, you might want to consider using the async API's that SqlCommand exposes, e.g. ExecuteReaderAsync which allow you to pass a cancellation token to them, so that cancellation is all handled by your single cancellation token.
You also need to ensure that the correct cancellation token is passed to the right invocation of your method:
Private Sub Search(sender As Object, e As EventArgs) Handles input.TextChanged
Dim input As String = Me.input.Text
Static source As CancellationTokenSource
If (Not source Is Nothing) Then source.Cancel()
source = New CancellationTokenSource()
Dim token = source.Token
Task.Factory.StartNew(Sub() Me.SearchingAsync(input, token))
End Sub
Basically, at this line of code:
Task.Factory.StartNew(Sub() Me.SearchingAsync(input, command, source.Token))
when it completes, all that you know is that, at some future point in time, it's going to run this:
Me.SearchingAsync(input, command, source.Token)
At that future point in time, it's going to load a SqlCommand object from the command variable and then invoke SearchingAsync (and, similarly, source is loaded at this point)
But, what if, in the meantime, your Search method has run again? It's cancelled the command and source that were originally intended for this method call, and it's replaced them with new copies. And it's scheduled another task to run SearchingAsync in the future. Those two calls end up referencing the same command objects and so it ends up with the #input parameter added to it twice.
I'm creating Async Tasks in a loop, The task has to read data from sql database.
as the source data size gets bigger, I got this error:
The query processor could not start the necessary thread resources for parallel query execution.
here is my code:
For Each objLocDataTable In pLocationData
If objLocDataTable IsNot Nothing AndAlso objLocDataTable.ISVALID Then
listLocationELTTasks.Add(TaskEx.Run(
Async Function() As Task
Dim resTable As System.Data.DataTable = Await mDBUtil.QueryAsync(strQry)
....... 'do calculation works based on the query result
End Function))
END if
Next
Public Async Function QueryAsync(ByVal strSqlQuery As String) As Task(Of DataTable)
Dim returnDataTable As DataTable = Nothing
Dim nTriedNumber As Integer = 0, nMaxTryNumber = 10
Dim bStop As Boolean = False
While (Not bStop)
Try
Using dbConnection As New SqlConnection(mStrConn)
Using dbComm As New SqlCommand(strSqlQuery, dbConnection)
Await dbConnection.OpenAsync()
dbComm.CommandType = CommandType.Text
Dim readerTask As Task(Of SqlDataReader) = Task.Factory.FromAsync(AddressOf dbComm.BeginExecuteReader, AddressOf dbComm.EndExecuteReader, Nothing)
returnDataTable = Await readerTask.ContinueWith(
Function(pervTask)
Dim resultTable As New DataTable()
Dim reader As SqlDataReader = Nothing
Try
reader = pervTask.Result
reader.Read()
resultTable.Load(reader)
bStop = True
Catch ex As Exception
System.Diagnostics.Debug.WriteLine(String.Format("Thread ID [{0}]: nTriedNumber [{1}], QueryAsync error: {2}", Threading.Thread.CurrentThread.ManagedThreadId, nTriedNumber, ex.Message))
Finally
If (reader IsNot Nothing) Then
reader.Close()
End If
If (Not dbConnection.State = ConnectionState.Closed) Then
dbConnection.Close()
End If
End Try
Return resultTable
End Function)
End Using
End Using
Catch ex As Exception
System.Diagnostics.Debug.WriteLine(String.Format("Not datareader error, nTriedNumber [{0}], QueryAsync error: {1}", nTriedNumber, ex.Message))
Finally
'could be shorten as if( returnDataTable Is Nothing or returnDataTable.Rows.Count() = 5) Then
System.Diagnostics.Debug.WriteLine(String.Format("Thread ID [{0}]: QueryAsync is sleeping for 1 sec", Threading.Thread.CurrentThread.ManagedThreadId))
System.Threading.Thread.Sleep(1000)
End If
If (nTriedNumber >= nMaxTryNumber) Then
System.Diagnostics.Debug.WriteLine(String.Format("Thread ID [{0}]: Reach to max try number, failed to retrieve data", Threading.Thread.CurrentThread.ManagedThreadId))
bStop = True
End If
End Try
End While
Return returnDataTable
End Function
As in function QueryAsync, I tried
- close the db connection as quick as possible.
- even sleep for a while
none of them working
Note:
- the sql data source is about 50 million records, I can NOT load it into memory at once before the for loop to do in-memory linq query.
I tried to replace the Task with Parrel.foreach, same result.
what's the efficient way to read data from sql database in task.
It looks like you don't need async IO. Switch to synchronous database calls, and your life becomes very easy:
pLocationData
.AsParallel()
.WithDegreeOfParallelism(8) //choose the number by testing
.ForEach(objLocDataTable => Query(objLocDataTable));
(C# syntax). I've renamed QueryAsync to Query because it should be a good old synchronous method like we have always used it.
This executes your queries with 8 threads concurrently. A fixed amount of threads will not overload the DB.