使用 Microsoft.Data.SqlClient 設定參數

參數會將值傳送給指令,與其 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)}");
}

參數名稱必須與儲存程序宣告的名稱相符。

設定表值參數

以資料表為值的參數會將多列傳送給儲存程序或參數化語句。 若要設定:

  1. 設定 SqlDbType.Structured。
  2. 設定 TypeName 為資料庫使用者定義的資料表類型。
  3. 指派一個 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 每次呼叫都需要額外的資料庫往返。 必要時在開發階段推導元資料,然後在效能敏感路徑中明確定義參數。