sp_executesql mit benutzerdefiniertem Tabellentyp, der sich nicht korrekt verhält

11

Problem : Gibt es ein bekanntes Problem mit benutzerdefinierten Tabellentypen als Parameter für sp_executesql ? Antwort - nein, ich bin ein Idiot.

Skript einrichten

Dieses Skript erstellt jeweils eine Tabelle, eine Prozedur und einen benutzerdefinierten Tabellentyp (nur eingeschränkter SQL Server 2008+).

  • Der Zweck des Heaps besteht darin, ein Audit bereitzustellen, bei dem die Daten in das Verfahren aufgenommen wurden. Es gibt keine Einschränkungen, nichts, was das Einfügen von Daten verhindern könnte.

  • Die Prozedur verwendet als Parameter einen benutzerdefinierten Tabellentyp. Alles, was der Prozess tut, ist in die Tabelle einzufügen.

  • Der benutzerdefinierte Tabellentyp ist ebenfalls sehr einfach, nur eine einzige Spalte

Ich habe Folgendes ausgeführt 11.0.1750.32 (X64) und 10.0.4064.0 (X64)Ja, ich weiß, dass die Box gepatcht werden könnte, ich kontrolliere das nicht.

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

Problemwiedergabe

Dieses Skript demonstriert das Problem und sollte fünf Sekunden dauern, um ausgeführt zu werden. Ich erstelle zwei Instanzen meiner benutzerdefinierten Tabellentypen und versuche dann, sie auf zwei verschiedene Arten zu übergeben sp_executesql. Die Parameterzuordnung im ersten Aufruf ahmt nach, was ich von SQL Profiler erfasst habe. Ich rufe dann die Prozedur ohne den sp_executesqlWrapper auf.

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

Ergebnisse : Unten sind meine Ergebnisse. Die Tabelle ist zunächst leer. Ich gebe die aktuelle Zeit aus und rufe die beiden an sp_executesql. Ich warte 5 Sekunden, bis die aktuelle Zeit verstrichen ist, und rufe dann die gespeicherte Prozedur selbst auf und speichere schließlich die Prüftabelle.

Wie Sie dem Zeitstempel entnehmen können, entspricht der Datensatz für den B-Datensatz dem direkten Aufruf der gespeicherten Prozedur. Es gibt auch keinen SQLC-Datensatz.

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

Skript abreißen

Dieses Skript entfernt die Objekte in der richtigen Reihenfolge (Sie können den Typ nicht löschen, bevor die Referenz der Prozedur entfernt wurde).

-- cleanup
DROP TABLE dbo.UDTT_holder
DROP PROCEDURE dbo.Repro
DROP TYPE dbo.UDTT
GO

Warum das wichtig ist

Der Code scheint albern, aber ich habe nicht viel Kontrolle über die Verwendung von sp_execute im Vergleich zu einem "direkten" Aufruf des Prozesses. Weiter oben in der Anrufkette verwende ich die ADO.NET-Bibliothek und übergebe einen TVP, wie ich es zuvor getan habe . Der Typ des Parameters ist korrekt auf System.Data.SqlDbType.Structured und der CommandType auf System.Data.CommandType.StoredProcedure festgelegt .

Warum ich ein Noob bin (bearbeiten)

Rob und Martin Smith haben gesehen, was ich nicht gesehen habe - die Anweisung, die an sp_executesql übergeben wurde, hat den @MetricDataParameter nicht verwendet . Ein nicht übergebener / zugeordneter Parameter wird nicht verwendet.

Ich habe meinen funktionierenden C # -Tabellenwert-Parametercode in PowerShell übersetzt, und da er kompiliert wurde, konnte es nicht der fehlerhafte Code sein. außer es war. Beim Schreiben dieser Frage wurde mir klar, dass ich das nicht auf gesetzt hatte CommandType, StoredProcedurealso fügte ich diese Zeile hinzu und führte den Code erneut aus, aber der Trace im Profiler änderte sich nicht, sodass ich davon ausging, dass dies nicht das Problem war.

Lustige Geschichte - Wenn Sie die aktualisierte Datei nicht speichern , macht das Ausführen keinen Unterschied. Ich bin heute Morgen angekommen und habe es erneut ausgeführt (nach dem Speichern) und ADO.NET hat es übersetzt, EXEC dbo.Repro @MetricData=@p3was gut funktioniert.

billinkc
quelle

Antworten:

10

sp_executesqldient zur Ausführung von Ad-hoc-T-SQL. Also sollten Sie versuchen:

EXECUTE sp_executesql N'exec dbo.Repro @MetricData',N'@MetricData dbo.UDTT READONLY',@MetricData=@p3
Rob Farley
quelle
1
+1 Derzeit hat das Skript im OP nur die Anweisung dbo.Repround verwendet die übergebenen Parameter überhaupt nicht.
Martin Smith
Ja, ich habe Robs Kommentar gesehen, und obwohl der EXEC-Teil dort nicht benötigt wurde, war es sinnvoll, dass der Parameter beim Aufruf nicht verwendet wurde, weil der Parameter im Skript nicht referenziert wurde. Aktualisierung des Tickets, um meine Dummheit
widerzuspiegeln
@billinkc - Ah, ich habe gerade eine Antwort mit einer detaillierteren Erklärung hinzugefügt und dann Ihren Kommentar gesehen. Ich werde diese Antwort dann gleich löschen.
Martin Smith
Richtig. Sie hatten nicht erwähnt, dass Sie dies von ado.net aus tun. sp_executesql entspricht einem .CommandText, nicht .StoredProcedure.
Rob Farley
3

Ich war versucht, diese Frage zu löschen, dachte aber, jemand anderes könnte auf einen ähnlichen Umstand stoßen.

Die Hauptursache war, dass ich $updateCommandden CommandType nicht festgelegt hatte. Der Standardwert ist Text, daher wurde meine gespeicherte Prozedur korrekt aufgerufen. Meine Parameter wurden erstellt und weitergegeben werden , sondern nur als in den anderen Antworten festgestellt , da die Parameter sind verfügbar , bedeutet nicht , sie werden verwendet .

Code für den Fall, dass jemand an meinem Fehler interessiert war

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

Das Ausführen mit dem Wert CommandType of Text würde die Lösung von @ rob_farley erfordern, den Wert von $updateQueryin "EXEC dbo.Repro @MetricData" zu ändern. Zu diesem Zeitpunkt ordnet sp_executesql die Werte korrekt zu und das Leben ist gut.

Das Ausführen mit einem CommandTypevon TableDirectwird vom SqlClient-Datenprovider nicht unterstützt, wie in der Dokumentation angegeben.

Die Verwendung CommandTypevon a StoredProcedureübersetzt in einen direkten Aufruf der gespeicherten Prozedur ohne den sp_executesqlWrapper und funktioniert hervorragend.

billinkc
quelle