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.