sp_executesql với loại bảng do người dùng xác định không hoạt động chính xác


11

Vấn đề : có một vấn đề đã biết với các loại bảng do người dùng định nghĩa là tham số cho sp_executesql không? Trả lời - không, tôi là một thằng ngốc.

Thiết lập kịch bản

Kịch bản lệnh này tạo một trong mỗi loại bảng, thủ tục và loại bảng do người dùng xác định (chỉ giới hạn SQL Server 2008+).

  • Mục đích của heap là cung cấp một cuộc kiểm toán rằng có, dữ liệu đã đưa nó vào quy trình. Không có ràng buộc, không có gì để ngăn chặn dữ liệu được chèn vào.

  • Quy trình lấy làm tham số loại bảng do người dùng xác định. Tất cả các Proc làm là chèn vào bảng.

  • Kiểu bảng do người dùng định nghĩa cũng đơn giản, chỉ là một cột

Tôi đã chạy theo sau 11.0.1750.32 (X64)10.0.4064.0 (X64)Có, tôi biết hộp đó có thể được vá, tôi không kiểm soát được.

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

Vấn đề sinh sản

Kịch bản này cho thấy vấn đề và sẽ mất năm giây để thực thi. Tôi tạo hai trường hợp loại bảng do người dùng xác định và sau đó thử hai phương tiện khác nhau để chuyển chúng vào sp_executesql. Ánh xạ tham số trong lệnh gọi đầu tiên bắt chước những gì tôi đã thu được từ SQL Profiler. Sau đó tôi gọi thủ tục mà không có sp_executesqltrình bao bọc.

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

Kết quả : dưới đây là kết quả của tôi. Bàn ban đầu trống. Tôi phát ra thời gian hiện tại và thực hiện hai cuộc gọi đến sp_executesql. Tôi đợi trong 5 giây để vượt qua và phát ra thời gian hiện tại, sau đó gọi chính thủ tục được lưu trữ và cuối cùng kết xuất bảng kiểm toán.

Như bạn có thể thấy từ dấu thời gian, bản ghi cho bản ghi B tương ứng với lời gọi thủ tục được lưu trữ thẳng. Ngoài ra, không có bản ghi 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

Xé kịch bản

Tập lệnh này xóa các đối tượng theo đúng thứ tự (bạn không thể loại bỏ trước khi tham chiếu của thủ tục đã bị xóa)

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

Tại sao điều này lại quan trọng

Mã này có vẻ ngớ ngẩn nhưng tôi không có nhiều quyền kiểm soát việc sử dụng sp_execute so với cuộc gọi "thẳng" tới Proc. Nâng cao chuỗi cuộc gọi, tôi đang sử dụng thư viện ADO.NET và chuyển qua TVP như tôi đã làm trước đây . Kiểu của tham số được đặt chính xác thành System.Data.SqlDbType.Sturationured và CommandType được đặt thành System.Data.CommandType.StoredProcedure .

Tại sao tôi là một người mới (chỉnh sửa)

Rob và Martin Smith đã thấy những gì tôi không thấy - câu lệnh được truyền cho sp_executesql không sử dụng @MetricDatatham số. Một tham số bỏ qua / ánh xạ sẽ không được sử dụng.

Tôi đã dịch mã tham số có giá trị bảng C # đang hoạt động của mình sang PowerShell và vì nó được biên dịch, nó không thể là mã bị lỗi; ngoại trừ nó đã được. Trong khi viết lên câu hỏi này, tôi nhận ra rằng tôi đã không đặt CommandTypeđể StoredProcedurenên tôi thêm vào dòng đó và tái chạy mã nhưng dấu vết trong hồ sơ không thay đổi vì vậy tôi cho rằng đó không phải là vấn đề.

Câu chuyện vui - nếu bạn không lưu tệp cập nhật, chạy nó sẽ không tạo ra sự khác biệt. Tôi đã nhận được vào sáng nay và chạy lại nó (sau khi lưu) và ADO.NET đã dịch nó để EXEC dbo.Repro @MetricData=@p3hoạt động tốt.

Câu trả lời:


10

sp_executesqllà để thực hiện T-SQL ad-hoc. Vì vậy, bạn nên thử:

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

1
+1 Hiện tại tập lệnh trong OP chỉ có câu lệnh dbo.Reprovà hoàn toàn không sử dụng thông số đã truyền.
Martin Smith

Yesssss, tôi đã thấy bình luận của Rob và trong khi nó không cần phần EXEC trong đó, thì có nghĩa là lý do tham số không được sử dụng trong cuộc gọi là tham số không được tham chiếu trong tập lệnh. Cập nhật vé để phản ánh sự ngu ngốc của tôi
billinkc

@billinkc - Ah tôi vừa thêm một câu trả lời với lời giải thích chi tiết hơn sau đó thấy bình luận của bạn. Tôi sẽ xóa câu trả lời đó sau một lát.
Martin Smith

Đúng. Bạn đã không đề cập đến việc bạn đang làm điều này từ ado.net. sp_executesql tương đương với .CommandText, không phải .StoredProcedure.
Rob Farley

3

Tôi đã cố gắng để xóa câu hỏi này nhưng hình dung người khác có thể gặp phải trường hợp tương tự.

Nguyên nhân gốc là tôi $updateCommandđã không đặt CommandType. Giá trị mặc định là Văn bản để quy trình được lưu trữ của tôi được gọi chính xác. Các tham số của tôi đã được tạo và thông qua nhưng như đã lưu ý trong các câu trả lời khác chỉ vì các tham số có sẵn không có nghĩa là chúng đang được sử dụng .

Mã trong trường hợp bất cứ ai quan tâm đến sai lầm của tôi

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

Chạy với giá trị CommandType của Văn bản sẽ yêu cầu giải pháp thay đổi giá trị của @ rob_farley là $updateQuery"EXEC dbo.Repro @MetricData". Tại thời điểm đó, sp_executesql sẽ ánh xạ các giá trị chính xác và cuộc sống rất tốt.

Chạy với một CommandTypetrong số TableDirectkhông được Nhà cung cấp dữ liệu SqlClient hỗ trợ, như tài liệu chỉ ra.

Sử dụng một CommandTypetrong các StoredProceduredịch để gọi một thủ tục được lưu trữ trực tiếp mà không có sp_executesqltrình bao bọc và hoạt động rất tốt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.