sp_executesql dengan tipe tabel yang ditentukan pengguna tidak berlaku dengan benar

11

Masalah : apakah ada masalah yang diketahui dengan tipe tabel yang ditentukan pengguna sebagai parameter untuk sp_executesql ? Jawab - tidak, saya idiot.

Siapkan skrip

Script ini membuat masing-masing masing-masing tabel, prosedur dan tipe tabel yang ditentukan pengguna (hanya SQL Server 2008+).

  • Tujuan tumpukan adalah untuk memberikan audit bahwa ya, data membuatnya menjadi prosedur. Tidak ada kendala, tidak ada apa pun untuk mencegah data dimasukkan.

  • Prosedur ini mengambil sebagai parameter tipe tabel yang ditentukan pengguna. Semua proc tidak dimasukkan ke dalam tabel.

  • Tipe tabel yang ditentukan pengguna juga sederhana, hanya satu kolom

Saya telah menjalankan yang berikut melawan 11.0.1750.32 (X64) dan 10.0.4064.0 (X64)Ya, saya tahu kotak itu bisa ditambal, saya tidak mengendalikan itu.

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

Masalah reproduksi

Script ini menunjukkan masalah dan harus memakan waktu lima detik untuk dieksekusi. Saya membuat dua contoh tipe tabel yang ditentukan pengguna dan kemudian mencoba dua cara yang berbeda untuk meneruskannya sp_executesql. Pemetaan parameter dalam doa pertama meniru apa yang saya ambil dari SQL Profiler. Saya kemudian memanggil prosedur tanpa sp_executesqlbungkus.

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

Hasil : di bawah ini adalah hasil saya. Tabel awalnya kosong. Saya memancarkan waktu saat ini dan melakukan dua panggilan ke Internet sp_executesql. Saya menunggu 5 detik untuk lulus dan memancarkan waktu saat ini diikuti dengan memanggil prosedur tersimpan itu sendiri dan akhirnya membuang tabel audit.

Seperti yang dapat Anda lihat dari stempel waktu, catatan untuk catatan B terkait dengan pemanggilan prosedur tersimpan langsung. Juga, tidak ada catatan 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

Runtuhkan skrip

Script ini menghapus objek dalam urutan yang benar (Anda tidak dapat menjatuhkan jenis sebelum referensi prosedur telah dihapus)

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

Mengapa ini penting?

Kode tampaknya konyol tapi saya tidak punya banyak kendali atas penggunaan sp_execute vs panggilan "langsung" ke proc. Semakin tinggi rantai panggilan, saya menggunakan perpustakaan ADO.NET dan mengirimkan TVP seperti yang telah saya lakukan sebelumnya . Tipe parameter diatur dengan benar ke System.Data.SqlDbType.Structured dan CommandType diatur ke System.Data.CommandType.StoredProcedure .

Mengapa saya seorang noob (edit)

Rob dan Martin Smith melihat apa yang tidak saya lihat - pernyataan yang dikirimkan ke sp_executesql tidak menggunakan @MetricDataparameter. Parameter yang tidak dilass / dipetakan tidak akan digunakan.

Saya menerjemahkan kode parameter C # table-rated-parameter saya ke PowerShell dan karena dikompilasi, itu bukan kode yang salah; kecuali itu. Saat menulis pertanyaan ini, saya menyadari saya tidak mengatur CommandTypeuntuk StoredProcedurejadi saya menambahkan baris itu dan re-lari kode tapi jejak di profiler tidak berubah sehingga aku menduga itu bukan masalah.

Cerita lucu - jika Anda tidak menyimpan file yang diperbarui, menjalankannya tidak akan membuat perbedaan. Saya masuk pagi ini dan menjalankannya kembali (setelah disimpan) dan ADO.NET menerjemahkannya EXEC dbo.Repro @MetricData=@p3yang berfungsi dengan baik.

billinkc
sumber

Jawaban:

10

sp_executesqluntuk mengeksekusi ad-hoc T-SQL. Jadi, Anda harus mencoba:

EXECUTE sp_executesql N'exec dbo.Repro @MetricData',N'@MetricData dbo.UDTT READONLY',@MetricData=@p3
Rob Farley
sumber
1
+1 Saat ini skrip di OP hanya memiliki pernyataan dbo.Reprodan tidak menggunakan parameter yang diteruskan sama sekali.
Martin Smith
Yesssss, saya melihat komentar Rob dan sementara itu tidak membutuhkan bagian EXEC di sana, masuk akal bahwa alasan parameter tidak digunakan dalam panggilan adalah parameter tidak dirujuk dalam skrip. Memperbarui tiket untuk mencerminkan kebodohan saya
billinkc
@ Billinkc - Ah saya baru saja menambahkan jawaban dengan penjelasan lebih rinci kemudian melihat komentar Anda. Saya akan menghapus jawaban itu sebentar lagi.
Martin Smith
Baik. Anda tidak menyebutkan bahwa Anda melakukan ini dari ado.net. sp_executesql setara dengan .CommandText, bukan .StoredProcedure.
Rob Farley
3

Saya tergoda untuk menghapus pertanyaan ini tetapi mengira orang lain mungkin akan mengalami keadaan yang serupa.

Penyebab utamanya adalah saya $updateCommandbelum menetapkan CommandType. Nilai standarnya adalah Teks sehingga prosedur tersimpan saya dipanggil dengan benar. Parameter saya sedang dibuat dan diteruskan tetapi seperti dicatat dalam jawaban lain hanya karena parameter tersedia tidak berarti mereka sedang digunakan .

Kode kalau-kalau ada yang tertarik dengan kesalahan saya

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

Menjalankan dengan nilai CommandType of Text akan membutuhkan solusi @ rob_farley untuk mengubah nilai $updateQuerymenjadi "EXEC dbo.Repro @MetricData". Pada titik itu, sp_executesql akan memetakan nilai dengan benar dan umurnya baik.

Berjalan dengan CommandTypedari TableDirecttidak didukung oleh Penyedia SqlClient Data, sebagai dokumentasi menunjukkan.

Menggunakan CommandTypedari StoredProcedurediterjemahkan ke doa langsung dari prosedur yang tersimpan tanpa sp_executesqlpembungkus dan karya besar.

billinkc
sumber