变量名在查询批处理或存储过程中必须是唯一的

时间:2014-04-25 08:50:40

标签: vb.net multithreading .net-4.0 sqlclient

我尝试实施异步搜索"引擎",但我遇到了一些困难。

出于某种原因,每隔一段时间就会抛出SqlException,说明:

  

"变量名称' @ input'已经宣布。变量名在查询批处理或存储过程中必须是唯一的。"

Sample application

示例应用

以下代码以sys.messages表为目标,因此您所要做的就是更改连接字符串

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

1 个答案:

答案 0 :(得分:1)

因为您在多次调用SqlCommand之间无意中共享了SearchingAsync个对象。我将删除尝试取消尝试的外部代码,并让SearchingAsync创建自己的非共享实例。

同时,您可能需要考虑使用SqlCommand公开的异步API,例如ExecuteReaderAsync允许您将取消令牌传递给他们,以便取消全部由您的单一取消令牌处理。

您还需要确保将正确的取消令牌传递给方法的正确调用:

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

基本上,在这行代码中:

Task.Factory.StartNew(Sub() Me.SearchingAsync(input, command, source.Token))

当它完成时,所有你知道的是,在未来的某个时间点,它将会运行:

Me.SearchingAsync(input, command, source.Token)

在未来的某个时间点,它将从SqlCommand变量加载command对象,然后调用SearchingAsync(类似地,source加载到Search这一点)

但是,如果在此期间你的command方法再次运行怎么办?它取消了最初用于此方法调用的sourceSearchingAsync,并将其替换为新副本。并且计划另一个任务在将来运行command。这两个调用最终会引用相同的@input个对象,因此最终会将{{1}}参数添加到其中两次。