參數會將值傳送給指令,與其 Transact-SQL(T-SQL)文字分開。 使用 SqlParameter 物件代替將值串接或插值到 SQL 語句中。 參數有助於防止 SQL 注入並保留值的資料庫類型。
本文範例假設一個名為 connection 的開啟 SqlConnection,其目標為 AdventureWorks 範例資料庫。
C# 範例需要 using Microsoft.Data.SqlClient;。 使用 SqlDbType 的範例也需要 using System.Data;。
新增輸入參數
在指令文本中為每個值的佔位符加入參數。 指定參數的 SQL Server 類型並指派其值:
const string sql = """
SELECT ProductID, Name, ListPrice
FROM Production.Product
WHERE ProductSubcategoryID = @subcategoryId
AND ListPrice >= @minimumPrice
ORDER BY ListPrice;
""";
using var command = new SqlCommand(sql, connection);
command.Parameters.Add("@subcategoryId", SqlDbType.Int).Value = 2;
command.Parameters.Add("@minimumPrice", SqlDbType.Money).Value = 1_000m;
using SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine(
$"{reader.GetInt32(0)}: {reader.GetString(1)} ({reader.GetDecimal(2):C})");
}
在指令文字和 SqlParameter 中,參數名稱請使用相同的大小寫。 SQL Server 透過伺服器定序來比對變數名稱,因此在使用區分大小寫的定序的伺服器上,若名稱不相符,便會因「必須宣告純量變數」錯誤而失敗。
使用參數預留位置符號
Microsoft.Data.SqlClient 使用 @parameterName 形式的命名參數。 不支援位置問號(?)佔位符。
參數佔位符代表值,而非 SQL 識別碼或語法。 你不能將參數用於資料表名稱、欄位名稱、排序方向或整個 SQL 片段。 如果應用程式動態選擇識別碼,請先根據允許清單驗證該識別碼,並在驗證後才將其加入指令文字中。 繼續將所有資料值當作參數傳遞。
對於文字指令,Microsoft.Data.SqlClient 透過 sp_executesql 將參數化語句傳送至 SQL Server。 當語句文字與參數定義保持不變時,SQL Server 可以重複使用執行計畫。
指定參數資料類型
當應用程式知道目標 SQL Server 類型時,明確設定SqlDbType。 同時也設定特定型別的中繼資料,如 Size、Precision 和 Scale,並在適用時設定。
此範例符合 Person.Person 中的 nvarchar(50) 及 datetime 欄位:
const string sql = """
SELECT BusinessEntityID, FirstName, LastName
FROM Person.Person
WHERE LastName = @lastName
AND ModifiedDate >= @modifiedSince
ORDER BY BusinessEntityID;
""";
using var command = new SqlCommand(sql, connection);
SqlParameter lastName = command.Parameters.Add(
"@lastName",
SqlDbType.NVarChar,
50);
lastName.Value = "O'Brien";
SqlParameter modifiedSince = command.Parameters.Add(
"@modifiedSince",
SqlDbType.DateTime);
modifiedSince.Value = new DateTime(2013, 1, 1);
將參數中繼資料與目標欄位匹配,可避免隱含轉換,並防止字串長度或十進位刻度的變更改變參數定義。
對於小數值,請明確設定精確度和小數位數。
Weight欄位Production.Product使用decimal(8, 2):
const string sql = """
SELECT ProductID, Name, Weight
FROM Production.Product
WHERE Weight >= @minimumWeight
ORDER BY Weight;
""";
using var command = new SqlCommand(sql, connection);
SqlParameter minimumWeight = command.Parameters.Add(
"@minimumWeight",
SqlDbType.Decimal);
minimumWeight.Precision = 8;
minimumWeight.Scale = 2;
minimumWeight.Value = 10.25m;
傳遞資料庫 NULL 值
指派 Value 以傳送資料庫 NULL。 對於一般純量參數,SQL Server NULL 並不代表 C# null:
const string sql = """
SELECT SalesOrderID, OrderDate, SalesPersonID
FROM Sales.SalesOrderHeader
WHERE (@salesPersonId IS NULL OR SalesPersonID = @salesPersonId)
ORDER BY SalesOrderID;
""";
using var command = new SqlCommand(sql, connection);
SqlParameter salesPersonId = command.Parameters.Add(
"@salesPersonId",
SqlDbType.Int);
int? selectedSalesPersonId = null;
salesPersonId.Value = selectedSalesPersonId.HasValue
? selectedSalesPersonId.Value
: DBNull.Value;
選擇性地使用推斷的元資料
此 AddWithValue 方法從執行時值推斷參數元資料。 當推論的元資料穩定且與資料庫類型相符時,推論非常方便。 當字串長度、十進位精度或其他值的特性改變時,它可以產生不同的參數定義。
優先使用搭配明確 SqlDbType 的 Add 來執行用於正式環境且目標結構已知的指令。 不要把 C# null 傳給 AddWithValue;改用指定型別並指派 DBNull.Value 。
重新使用指令參數
只建立一次參數,在執行相同指令但使用不同值時,只變更其 Value:
const string sql = """
SELECT TOP (1) Name, ListPrice
FROM Production.Product
WHERE ProductSubcategoryID = @subcategoryId
ORDER BY ListPrice DESC;
""";
using var command = new SqlCommand(sql, connection);
SqlParameter subcategoryId = command.Parameters.Add(
"@subcategoryId",
SqlDbType.Int);
foreach (int id in new[] { 1, 2, 3 })
{
subcategoryId.Value = id;
using SqlDataReader reader = command.ExecuteReader();
if (reader.Read())
{
Console.WriteLine($"{id}: {reader.GetString(0)}");
}
}
不要在每次執行時再加一個同名參數。 沿用現有參數,或在重建前先清除集合。
設定參數方向
這個 Direction 屬性用來識別指令如何使用參數。 將其設為 ParameterDirection 值:
| 方向 | 使用 |
|---|---|
| Input | 將值傳送到語句或儲存程序。 這是預設值。 |
| Output | 從儲存程序的輸出參數接收一個值。 |
| InputOutput | 傳送初始值並接收更新後的值。 |
| ReturnValue | 從 T-SQL RETURN 陳述式接收整數值。 |
對於輸出、輸入/輸出及回傳值參數,僅在指令結束後讀取 Value 。 如果指令回傳了資料列,請在讀取這些值前關閉或丟棄該 SqlDataReader
完整儲存程序範例請參見 「用儲存程序修改資料」。
使用參數搭配儲存程序
設定 StoredProcedure 並使用程序的參數名稱。 此範例呼叫 AdventureWorks 中包含的 dbo.uspGetEmployeeManagers 程序:
using var command = new SqlCommand(
"dbo.uspGetEmployeeManagers",
connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(
"@BusinessEntityID",
SqlDbType.Int).Value = 6;
using SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Console.WriteLine(
$"{reader.GetString(2)} {reader.GetString(3)}");
}
參數名稱必須與儲存程序宣告的名稱相符。
設定表值參數
以資料表為值的參數會將多列傳送給儲存程序或參數化語句。 若要設定:
- 設定
SqlDbType.Structured。 - 設定 TypeName 為資料庫使用者定義的資料表類型。
- 指派一個 DataTable、 DbDataReader或
IEnumerable<SqlDataRecord>列的來源。
具有已定義欄位且沒有資料列的 DataTable 會傳送空資料表。 空的 IEnumerable<SqlDataRecord> 會擲回 ArgumentException;指派 C# null 以傳送該輸入表單的零列。 不要將 DBNull.Value 指派給表值參數。
關於型別建立、列映射及完整範例,請參見 Table-valued parameters。
設定串流值
將 TextReader、XmlReader 或 Value 指派給 Stream,以逐步提供較大的值。 對於有界記憶體傳輸,請使用 (max)、 XML 或 JSON 參數型別。 Microsoft.Data.SqlClient 會在執行前先緩衝來源,用於非(max)字元、二進位型態以及舊有大型物件型態。
參數類型必須與以下值相符:
| Value | 相容 SqlDbType 值 |
|---|---|
| Stream |
Binary、VarBinary 或舊版 Image 類型 |
| TextReader |
Char、NChar、NVarChar、VarChar、舊版 Text 與 NText 類型、Xml,或 Json |
| XmlReader |
Char、NChar、NVarChar、VarChar、舊版 Text 與 NText 類型、Xml 或 Json |
當物件類型、SqlDbType 或宣告大小不相容時,Microsoft.Data.SqlClient 會拋出 ArgumentException、InvalidCastException 或 SqlTypeException。 完整的上傳與下載範例,請參閱 SqlClient 串流支援。
推導參數資訊
DeriveParameters查詢 SQL Server 以取得儲存程序的參數元資料,並替換指令現有的參數集合:
using var command = new SqlCommand(
"dbo.uspGetEmployeeManagers",
connection);
command.CommandType = CommandType.StoredProcedure;
SqlCommandBuilder.DeriveParameters(command);
command.Parameters["@BusinessEntityID"].Value = 6;
DeriveParameters 每次呼叫都需要額外的資料庫往返。 必要時在開發階段推導元資料,然後在效能敏感路徑中明確定義參數。