sp_executesql с пользовательским типом таблицы не работает правильно

11

Проблема : есть известная проблема с определяемыми пользователем типами таблиц в качестве параметров sp_executesql ? Ответ - нет, я идиот.

Настроить скрипт

Этот сценарий создает по одной таблице, процедуре и определенному пользователем типу таблицы (только для ограниченного SQL Server 2008+).

  • Цель кучи состоит в том, чтобы предоставить аудит, который да, данные включили его в процедуру. Там нет никаких ограничений, нет ничего, чтобы предотвратить вставку данных.

  • Процедура принимает в качестве параметра определенный пользователем тип таблицы. Все, что делает процесс, это вставить в таблицу.

  • Определяемый пользователем тип таблицы также очень прост, только один столбец

Я запустил следующее 11.0.1750.32 (X64) и 10.0.4064.0 (X64)да, я знаю, что окно может быть исправлено, я не контролирую это.

-- this table record that something happened
CREATE TABLE dbo.UDTT_holder
(
    ServerName varchar(200)
,   insert_time datetime default(current_timestamp)
)
GO

-- user defined table type transport mechanism
CREATE TYPE dbo.UDTT
AS TABLE
(
    ServerName varchar(200)
)
GO

-- stored procedure to reproduce issue
CREATE PROCEDURE dbo.Repro
(
    @MetricData dbo.UDTT READONLY
)
AS
BEGIN
    SET NOCOUNT ON

    INSERT INTO dbo.UDTT_holder 
    (ServerName)
    SELECT MD.* FROM @MetricData MD
END
GO

Проблема воспроизведения

Этот скрипт демонстрирует проблему и должен занять пять секунд для выполнения. Я создаю два экземпляра определенных пользователем типов таблиц, а затем пробую два разных способа их передачи sp_executesql. Отображение параметров в первом вызове имитирует то, что я записал из SQL Profiler. Затем я вызываю процедуру без sp_executesqlоболочки.

SET NOCOUNT ON
DECLARE 
    @p3 dbo.UDTT
,   @MetricData dbo.UDTT
INSERT INTO @p3 VALUES(N'SQLB\SQLB')
INSERT INTO @MetricData VALUES(N'SQLC\SQLC')

-- nothing up my sleeve
SELECT * FROM dbo.UDTT_holder

SELECT CONVERT(varchar(24), current_timestamp, 121) + ' Firing sp_executesql' AS commentary
-- This does nothing
EXECUTE sp_executesql N'dbo.Repro',N'@MetricData dbo.UDTT READONLY',@MetricData=@p3
-- makes no matter if we're mapping variables
EXECUTE sp_executesql N'dbo.Repro',N'@MetricData dbo.UDTT READONLY',@MetricData

-- Five second delay
waitfor delay '00:00:05'
SELECT CONVERT(varchar(24), current_timestamp, 121) + ' Firing proc' AS commentary
-- this does
EXECUTE dbo.Repro @p3

-- Should only see the latter timestamp
SELECT * FROM dbo.UDTT_holder

GO

Результаты : ниже приведены мои результаты. Таблица изначально пуста. Я излучаю текущее время и делаю два звонка в sp_executesql. Я жду 5 секунд, чтобы пройти и выдать текущее время, затем вызвать саму хранимую процедуру и, наконец, сбросить таблицу аудита.

Как видно из отметки времени, запись для записи B соответствует прямому вызову хранимой процедуры. Также нет записи SQLC.

ServerName                                       insert_time
------------------------------------------------------------------------

commentary
-------------------------------------------------
2012-02-02 13:09:05.973 Firing sp_executesql

commentary
-------------------------------------------------
2012-02-02 13:09:10.983 Firing proc

ServerName                                       insert_time
------------------------------------------------------------------------
SQLB\SQLB                                        2012-02-02 13:09:10.983

Сносить сценарий

Этот скрипт удаляет объекты в правильном порядке (вы не можете удалить тип, пока не будет удалена ссылка на процедуру)

-- cleanup
DROP TABLE dbo.UDTT_holder
DROP PROCEDURE dbo.Repro
DROP TYPE dbo.UDTT
GO

Почему это важно

Код кажется глупым, но у меня нет большого контроля над использованием sp_execute против «прямого» вызова proc. Выше цепочки вызовов я использую библиотеку ADO.NET и передаю TVP, как я делал ранее . Тип параметра правильно установлен в System.Data.SqlDbType.Structured, а CommandType установлен в System.Data.CommandType.StoredProcedure .

Почему я нуб (править)

Роб и Мартин Смит увидели то, чего я не видел - оператор, передаваемый в sp_executesql, не использовал этот @MetricDataпараметр. Неиспользованный / сопоставленный параметр не будет использоваться.

Я переводил свой рабочий код табличных параметров C # в PowerShell, и, поскольку он компилировался, он не мог быть ошибочным кодом; кроме как было. При написании этого вопроса я понял, что не установил CommandTypeзначение, StoredProcedureпоэтому добавил эту строку и повторно запустил код, но трассировка в профилировщике не изменилась, поэтому я предположил, что это не проблема.

Забавная история - если вы не сохраните обновленный файл, его запуск не будет иметь значения. Я получил это утро и перезапустил его (после сохранения), и ADO.NET перевел его, EXEC dbo.Repro @MetricData=@p3который работает просто отлично.

billinkc
источник

Ответы:

10

sp_executesqlдля выполнения специального T-SQL. Итак, вы должны попробовать:

EXECUTE sp_executesql N'exec dbo.Repro @MetricData',N'@MetricData dbo.UDTT READONLY',@MetricData=@p3
Роб Фарли
источник
1
+1 В настоящее время скрипт в OP просто имеет оператор dbo.Reproи не использует переданные параметры вообще.
Мартин Смит
Да, я видел комментарий Роба, и, хотя там не требовалась часть EXEC, все же имел смысл, что причина, по которой параметр не использовался в вызове, заключалась в том, что этот параметр не был указан в сценарии. Обновление билета, чтобы отразить мою глупость
billinkc
@billinkc - А, я просто добавил ответ с более подробным объяснением, а потом увидел твой комментарий. Я удалю этот ответ немного позже.
Мартин Смит
Правильно. Вы не упоминали, что делаете это с ado.net. Процедура sp_executesql эквивалентна .CommandText, а не .StoredProcedure.
Роб Фарли
3

Я хотел удалить этот вопрос, но подумал, что кто-то еще может столкнуться с подобными обстоятельствами.

Основной причиной было то, что я $updateCommandне установил CommandType. Значением по умолчанию является Text, поэтому моя хранимая процедура вызывалась правильно. Мои параметры создавались и передавались, но, как отмечалось в других ответах, только то, что параметры доступны , не означает, что они используются .

Код на случай, если кто-то заинтересовался моей ошибкой

    $updateConnection = New-Object System.Data.SqlClient.SqlConnection("Data Source=localhost\localsqla;Initial Catalog=baseline;Integrated Security=SSPI;")
    $updateConnection.Open()

    $updateCommand = New-Object System.Data.SqlClient.SqlCommand($updateQuery)
    $updateCommand.Connection = $updateConnection

    # This is what I was missing
    $updateCommand.CommandType = [System.Data.CommandType]::StoredProcedure
    #$updateCommand.CommandType = [System.Data.CommandType]::Text
    #$updateCommand.CommandType = [System.Data.CommandType]::TableDirect

    $DataTransferFormat = $updateCommand.Parameters.AddWithValue("@MetricData", $dataTable)
    $DataTransferFormat.SqlDbType = [System.Data.SqlDbType]::Structured
    $DataTransferFormat.TypeName = $udtt
    $results = $updateCommand.ExecuteNonQuery()

Запуск со значением CommandType of Text потребует решения @ rob_farley об изменении значения $updateQueryна «EXEC dbo.Repro @MetricData». В этот момент sp_executesql правильно отобразит значения, и жизнь будет хорошей.

Работа с параметром CommandTypeof TableDirectне поддерживается провайдером данных SqlClient, как указано в документации.

Использование CommandTypeof StoredProcedureприводит к прямому вызову хранимой процедуры без sp_executesqlоболочки и прекрасно работает.

billinkc
источник