Problema : ¿hay algún problema conocido con los tipos de tabla definidos por el usuario como parámetros para sp_executesql ? Respuesta: no, soy un idiota.
Configurar guión
Este script crea una tabla, procedimiento y tipo de tabla definida por el usuario (solo SQL Server 2008+ restringido).
El propósito del montón es proporcionar una auditoría que sí, los datos llegaron al procedimiento. No hay restricciones, no hay nada que impida la inserción de datos.
El procedimiento toma como parámetro un tipo de tabla definido por el usuario. Todo lo que hace el proceso es insertarlo en la tabla.
El tipo de tabla definido por el usuario también es muy simple, solo una columna
He ejecutado lo siguiente 11.0.1750.32 (X64)
y 10.0.4064.0 (X64)
sí, sé que esa caja podría ser parcheada, no lo controlo.
-- 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
Reproducción problemática
Este script demuestra el problema y debe tardar cinco segundos en ejecutarse. Creo dos instancias de mis tipos de tablas definidas por el usuario y luego pruebo dos formas diferentes de pasarlas sp_executesql
. La asignación de parámetros en la primera invocación imita lo que capturé de SQL Profiler. Luego llamo al procedimiento sin el sp_executesql
envoltorio.
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
Resultados : a continuación están mis resultados. La tabla está inicialmente vacía. Emito la hora actual y hago las dos llamadas al sp_executesql
. Espero 5 segundos para pasar y emitir el tiempo actual seguido de llamar al procedimiento almacenado y finalmente volcar la tabla de auditoría.
Como puede ver en la marca de tiempo, el registro para el registro B corresponde a la invocación directa del procedimiento almacenado. Además, no hay registro 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
Derribar guión
Este script elimina los objetos en el orden correcto (no puede descartar el tipo antes de que se haya eliminado la referencia del procedimiento)
-- cleanup
DROP TABLE dbo.UDTT_holder
DROP PROCEDURE dbo.Repro
DROP TYPE dbo.UDTT
GO
Por qué esto importa
El código parece tonto, pero no tengo mucho control sobre el uso de sp_execute frente a una llamada "directa" al proceso. Más arriba en la cadena de llamadas, estoy usando la biblioteca ADO.NET y estoy pasando un TVP como lo hice anteriormente . El tipo de parámetro está configurado correctamente en System.Data.SqlDbType.Structured y CommandType está configurado en System.Data.CommandType.StoredProcedure .
Por qué soy un novato (editar)
Rob y Martin Smith vieron lo que no estaba viendo: la declaración que se pasó a sp_executesql no utilizó el @MetricData
parámetro. Un parámetro sin pasar / mapeado no se va a usar.
Estaba traduciendo mi código de parámetro de tabla de valores C # a PowerShell y, dado que se compiló, no podía ser el código el que tenía la culpa; excepto que lo fue. Mientras que la redacción de esta pregunta, me di cuenta que no había puesto el CommandType
a StoredProcedure
lo que añade que la línea y volver a correr el código, pero la traza de generador de perfiles no cambió así que supuse que no era el problema.
Historia divertida: si no guarda el archivo actualizado, ejecutarlo no hará la diferencia. Llegué esta mañana y lo volví a ejecutar (después de guardar) y ADO.NET lo tradujo a lo EXEC dbo.Repro @MetricData=@p3
que funciona bien.
dbo.Repro
y no usa los parámetros pasados en absoluto.