動態構建 WHERE id IN (...) 時,直接拼接字符串存在注入風險且無法參數化。如何安全高效傳遞列表參數?
| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| 字符串分割函數 (STRING_SPLIT / 自定義) | SQL Server 2016+,簡單列表 | 無需修改調用端,兼容舊代碼 | 性能一般,類型轉換隱患 |
| 表值參數 (TVP) | 大量數據、強類型、頻繁調用 | 最佳性能,編譯期類型檢查,計劃緩存友好 | 需創建用戶定義表類型,ADO.NET 需 DataTable |
| JSON 參數 (OPENJSON) | SQL Server 2016+,前端已序列化 JSON | 靈活,無需創建類型 | 解析開銷稍大 |
| 動態 SQL + 參數化 (sp_executesql) | 極簡列表、臨時方案 | 無額外對象 | 計劃緩存碎片化,維護困難 |
CREATE TYPE dbo.IdList AS TABLE (Id INT PRIMARY KEY);
CREATE PROC GetOrders @Ids IdList READONLY
AS
SELECT * FROM Orders WHERE OrderId IN (SELECT Id FROM @Ids);
var ids = new[] { 1001, 1002, 1003 };
var dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
foreach (var id in ids) dt.Rows.Add(id);
using (var cmd = new SqlCommand("GetOrders", conn) { CommandType = CommandType.StoredProcedure }) {
var param = cmd.Parameters.AddWithValue("@Ids", dt);
param.SqlDbType = SqlDbType.Structured;
param.TypeName = "dbo.IdList";
using (var reader = cmd.ExecuteReader()) { /* ... */ }
}
-- 僅適合臨時/小數據量
DECLARE @csv NVARCHAR(MAX) = '1001,1002,1003';
SELECT * FROM Orders
WHERE OrderId IN (SELECT CAST(value AS INT) FROM STRING_SPLIT(@csv, ','));
C# 傳入逗號分隔字符串即可,但**無法利用索引求值**,大表慎用。
DECLARE @json NVARCHAR(MAX) = '[1001,1002,1003]';
SELECT * FROM Orders
WHERE OrderId IN (SELECT [value] FROM OPENJSON(@json));