Problema : esiste un problema noto con i tipi di tabella definiti dall'utente come parametri per sp_executesql ? Risposta: no, sono un idiota.
Imposta script
Questo script crea una tabella, una procedura e un tipo di tabella definite dall'utente (solo per SQL Server 2008+ limitato).
Lo scopo dell'heap è quello di fornire una verifica che sì, i dati sono entrati nella procedura. Non ci sono vincoli, niente per impedire l'inserimento di dati.
La procedura accetta come parametro un tipo di tabella definito dall'utente. Tutto ciò che proc fa è inserire nella tabella.
Anche il tipo di tabella definito dall'utente è semplice, solo una singola colonna
Ho eseguito il seguente contro 11.0.1750.32 (X64)
e 10.0.4064.0 (X64)
Sì, so che la scatola potrebbe essere patchata, non lo controllo.
-- 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
Riproduzione dei problemi
Questo script dimostra il problema e dovrebbe richiedere cinque secondi per essere eseguito. Creo due istanze dei tipi di tabella definiti dall'utente e quindi provo due modi diversi di passarli sp_executesql
. La mappatura dei parametri nella prima invocazione imita ciò che ho acquisito da SQL Profiler. Quindi chiamo la procedura senza il sp_executesql
wrapper.
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
Risultati : di seguito sono riportati i miei risultati. La tabella è inizialmente vuota. Emetto l'ora corrente e faccio le due chiamate al sp_executesql
. Aspetto che passino 5 secondi ed emetta l'ora corrente seguita chiamando la procedura memorizzata stessa e infine scaricando la tabella di controllo.
Come si può vedere dal timestamp, il record per il record B corrisponde all'invocazione della procedura memorizzata diretta. Inoltre, non esiste alcun record 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
Scritto giù
Questo script rimuove gli oggetti nell'ordine corretto (non è possibile eliminare il tipo prima che sia stato rimosso il riferimento della procedura)
-- cleanup
DROP TABLE dbo.UDTT_holder
DROP PROCEDURE dbo.Repro
DROP TYPE dbo.UDTT
GO
Perché questo conta
Il codice sembra sciocco ma non ho molto controllo sull'uso di sp_execute rispetto a una chiamata "diretta" al proc. Più in alto nella catena di chiamate, sto usando la libreria ADO.NET e passando un TVP come ho fatto in precedenza . Il tipo di parametro è impostato correttamente su System.Data.SqlDbType.Structured e CommandType è impostato su System.Data.CommandType.StoredProcedure .
Perché sono un noob (modifica)
Rob e Martin Smith hanno visto ciò che non stavo vedendo: l'istruzione passata a sp_executesql non utilizzava il @MetricData
parametro. Un parametro non superato / mappato non verrà utilizzato.
Stavo traducendo il mio codice di parametri con valori di tabella C # funzionante in PowerShell e poiché è stato compilato, non poteva essere il codice in errore; tranne che lo era. Durante la stesura di questa domanda, mi sono reso conto di non aver impostato CommandType
su, StoredProcedure
quindi ho aggiunto quella riga e rieseguito il codice ma la traccia nel profiler non è cambiata, quindi ho pensato che non fosse il problema.
Storia divertente: se non si salva il file aggiornato, eseguirlo non farà differenza. Sono arrivato stamattina e l'ho eseguito di nuovo (dopo averlo salvato) e ADO.NET lo ha tradotto in modo EXEC dbo.Repro @MetricData=@p3
che funzioni perfettamente.
dbo.Repro
e non utilizza affatto i parametri passati.