Имена переменных должны быть уникальными в пакете запроса или хранимой процедуре
Я пытаюсь реализовать асинхронный поисковый "движок", но я сталкиваюсь с некоторыми трудностями.
По какой-то причине 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
Параметр добавлен в него дважды.