Pregunta

Problema: ¿Existe un problema conocido con los tipos de tabla definidos por el usuario como parámetros para sp_executesql? Respuesta - No, soy un idiota.

Configurar script

Este script crea uno de cada uno de los tipos de tabla de tabla, procedimiento y definido 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 para evitar que se inserten datos.

  • El procedimiento toma como parámetro un tipo de tabla definido por el usuario. Todo lo que el proceso hace es insertar en la tabla.

  • El tipo de tabla definido por el usuario también es simple, solo una columna

He corrido lo siguiente contra 11.0.1750.32 (X64) y 10.0.4064.0 (X64) Sí, sé que esa caja podría estar 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 de problemas

Este script demuestra el problema y debería tardar cinco segundos en ejecutarse. Creo dos instancias de mis tipos de tabla definidos por el usuario y luego pruebo dos medios diferentes para pasarlos a sp_executesql. El mapeo de parámetros en la primera invocación imita lo que capturé de SQL Profiler. Luego llamo al procedimiento sin el sp_executesql envoltura.

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 se presentan mis resultados. La tabla está inicialmente vacía. Emito la hora actual y hago las dos llamadas al sp_executesql. Espero que pasen 5 segundos y emitan la hora actual seguida de llamar al procedimiento almacenado en sí y finalmente volcar la tabla de auditoría.

Como puede ver en la marca de tiempo, el registro del registro B corresponde a la invocación de procedimiento almacenado recto. 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 el guión

Este script elimina los objetos en el orden correcto (no puede soltar 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 del SP_EXECUTE frente a una llamada "directa" al proceso. Más arriba en la cadena de llamadas, estoy usando la biblioteca ADO.NET y pasando un TVP como yo he hecho anteriormente. El tipo de parámetro se establece correctamente en System.data.sqldbtype.estructurado y el comandtype está configurado en System.data.commandtype.soredprocedure.

Por qué soy un novato (editar)

Rob y Martin Smith vieron lo que no estaba viendo: la declaración se pasó a sp_executesql no usó el @MetricData parámetro. Un parámetro no asignado/mapeado no se utilizará.

Estaba traduciendo mi código de parámetro de mesa con valor de tabla C# de trabajo a PowerShell y desde que compiló, no pude ser el código culpable; Excepto que era. Mientras escribía esta pregunta, me di cuenta de que no había establecido el CommandType a StoredProcedure Así que agregué esa línea y volví a manejar el código, pero el rastro en Profiler no cambió, así que supuse que ese no era el problema.

Historia divertida - Si no lo haces ahorrar El archivo actualizado, que se ejecuta no marcará la diferencia. Entré esta mañana y lo volví a manejar (después de guardar) y Ado.net lo tradujo a EXEC dbo.Repro @MetricData=@p3 que funciona bien.

¿Fue útil?

Solución

sp_executesql es para ejecutar T-SQL ad-hoc. Entonces deberías intentarlo:

EXECUTE sp_executesql N'exec dbo.Repro @MetricData',N'@MetricData dbo.UDTT READONLY',@MetricData=@p3

Otros consejos

Tuve la tentación de eliminar esta pregunta, pero pensé que alguien más podría toparse con una circunstancia similar.

La causa raíz fue mi $updateCommand no había establecido el ComandyPe. El valor predeterminado es el texto, por lo que mi procedimiento almacenado se llamaba correctamente. Mis parámetros se crearon y pasaron, pero como se señaló en las otras respuestas solo porque los parámetros son disponible no significa que estén siendo usó.

Código en caso de que alguien estuviera interesado en mi error

    $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()

Ejecutar con un valor de CommandType de texto requeriría la solución de @rob_farley de cambiar el valor de $updateQuery a "Ejec DBO.Repro @MetricData". En ese punto, el SP_EXECutesql mapeará los valores correctamente y la vida es buena.

Corriendo con un CommandType de TableDirect no es compatible con el proveedor de datos SQLClient, como lo indica la documentación.

Usando un CommandType de StoredProcedure se traduce en una invocación directa del procedimiento almacenado sin el sp_executesql envoltura y funciona muy bien.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a dba.stackexchange
scroll top