Имена переменных должны быть уникальными в пакете запроса или хранимой процедуре

Я пытаюсь реализовать асинхронный поисковый "движок", но я сталкиваюсь с некоторыми трудностями.

По какой-то причине SqlException выдается время от времени, заявляя, что:

"Имя переменной" @input "уже объявлено. Имена переменных должны быть уникальными в пакете запроса или хранимой процедуре".

Образец заявки

Образец заявки

Следующий код предназначен для таблицы 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 ответ

Решение

Потому что вы случайно делитесь SqlCommand объекты между несколькими вызовами SearchingAsync, Я бы удалил внешний код, который пытается справиться с попыткой отменить их, и просто позволить SearchingAsync создавать свои собственные, неразделенные экземпляры.

В то же время, вы можете рассмотреть возможность использования асинхронных API, которые SqlCommand выставляет, например 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 а также source которые изначально предназначались для этого вызова метода, и он заменил их новыми копиями. И запланировано еще одно задание для запуска SearchingAsync в будущем. Эти два вызова в конечном итоге ссылаются на один и тот же command объекты и поэтому он заканчивается @input Параметр добавлен в него дважды.

Другие вопросы по тегам