sp_executesql con il tipo di tabella definito dall'utente non si comporta correttamente


11

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

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 @MetricDataparametro. 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 CommandTypesu, StoredProcedurequindi 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=@p3che funzioni perfettamente.

Risposte:


10

sp_executesqlè per l'esecuzione di T-SQL ad hoc. Quindi dovresti provare:

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

1
+1 Attualmente lo script nell'OP ha solo l'istruzione dbo.Reproe non utilizza affatto i parametri passati.
— Martin Smith,

Sì, ho visto il commento di Rob e anche se non aveva bisogno della parte EXEC, aveva senso che la ragione per cui il parametro non veniva utilizzato con la chiamata era il parametro a cui non faceva riferimento nello script. Aggiornamento del biglietto per riflettere la mia follia
— Billinkc

@billinkc - Ah, ho appena aggiunto una risposta con spiegazioni più dettagliate, quindi ho visto il tuo commento. Eliminerò quella risposta tra poco.
— Martin Smith,

Giusto. Non avevi detto che lo stavi facendo da ado.net. sp_executesql è equivalente a un .CommandText, non .StoredProcedure.
— Rob Farley,

3

Sono stato tentato di eliminare questa domanda, ma ho pensato che qualcun altro potesse imbattersi in una circostanza simile.

La causa principale $updateCommandera che non avevo impostato CommandType. Il valore predefinito è Testo, quindi la mia procedura memorizzata veniva chiamata correttamente. I miei parametri sono stati creati e passati, ma come notato nelle altre risposte solo perché i parametri sono disponibili non significa che vengano utilizzati .

Codice nel caso qualcuno fosse interessato al mio errore

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

L'esecuzione con un valore di CommandType of Text richiederebbe la soluzione di @ rob_farley di modificare il valore di $updateQuery"EXEC dbo.Repro @MetricData". A quel punto, sp_executesql mapperà i valori correttamente e la vita è buona.

L'esecuzione con a CommandTypeof TableDirectnon è supportata dal fornitore di dati SqlClient, come indica la documentazione.

L'uso CommandTypedi a si StoredProceduretraduce in una chiamata diretta della procedura memorizzata senza il sp_executesqlwrapper e funziona alla grande.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.