sp_executesql con el tipo de tabla definido por el usuario no se comporta correctamente


11

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_executesqlenvoltorio.

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 @MetricDatapará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 CommandTypea StoredProcedurelo 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=@p3que funciona bien.

Respuestas:


10

sp_executesqles para ejecutar T-SQL ad-hoc. Entonces deberías probar:

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

1
+1 Actualmente, el script en el OP solo tiene la declaración dbo.Reproy no usa los parámetros pasados ​​en absoluto.
Martin Smith

Sí, vi el comentario de Rob y aunque no necesitaba la parte EXEC allí, tenía sentido que la razón por la que el parámetro no se estaba utilizando con la llamada era que el parámetro no estaba referenciado en el script.
Boleto de

@billinkc - Ah, acabo de agregar una respuesta con una explicación más detallada y luego vi tu comentario. Eliminaré esa respuesta en un momento entonces.
Martin Smith

Derecha. No habías mencionado que estabas haciendo esto desde ado.net. sp_executesql es equivalente a un .CommandText, no .StoredProcedure.
Rob Farley

3

Estuve tentado de eliminar esta pregunta, pero pensé que alguien más podría encontrarse en una situación similar.

La causa raíz era que $updateCommandno había configurado el CommandType. El valor predeterminado es Texto, por lo que se llamó correctamente a mi procedimiento almacenado. Mis parámetros se estaban creando y pasando, pero como se señaló en las otras respuestas solo porque los parámetros están disponibles no significa que se estén utilizando .

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 of Text requeriría la solución de @ rob_farley de cambiar el valor de $updateQuery"EXEC dbo.Repro @MetricData". En ese punto, sp_executesql asignará los valores correctamente y la vida es buena.

Correr con una CommandTypede las TableDirectno es compatible con el proveedor de datos SqlClient, como indica la documentación.

El uso CommandTypede a se StoredProceduretraduce en una invocación directa del procedimiento almacenado sin el sp_executesqlcontenedor y funciona muy bien.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.