執行命令

使用 SqlCommand 執行 Transact-SQL(T-SQL)陳述式或預存程序。 指令需要一個開啟的 SqlConnection。 使用後丟棄命令物件,並在重複使用連線前丟棄任何讀取器物件。

本文範例假設名為 connection 的開啟 SqlConnection 以 AdventureWorks 範例資料庫 為目標。

C# 範例需要 using Microsoft.Data.SqlClient;。 使用 SqlDbType、CommandType 或 CommandBehavior 的範例也需要 using System.Data;。

關於連線設定、認證與加密,請參見 連線字串 及 加密與憑證驗證。

選擇執行方法

選擇符合預期結果的方法:

方法 何時使用 結果
ExecuteNonQuery 指令會變更資料或綱要。 受影響的行數。 當沒有可用的計數時(包括 SET NOCOUNT ON 抑制計數時),可能會發生 -1。
ExecuteScalar 你需要一個數值。 第一欄的第一列,或當結果沒有列時為 null。 NULL資料庫會以 Value 傳回。
ExecuteReader 你需要資料列、多個結果集或結構。 A SqlDataReader。
ExecuteXmlReader 指令回傳 ExecuteXmlReader 所需形式的 XML。 一個XmlReader。

每種方法都有基於任務的非同步對應方法。 當呼叫端需要停止等待時,請傳入 CancellationToken。 關於取消與逾時行為,請參見 非同步程式設計。

執行文字指令

CommandType.Text 是預設值。 將陳述句放入 CommandText,並透過參數傳遞值:

const string sql = """
    SELECT ProductID, Name, ListPrice
    FROM Production.Product
    WHERE ProductSubcategoryID = @subcategoryId
    ORDER BY ListPrice DESC;
    """;

using var command = new SqlCommand(sql, connection);
command.Parameters.Add("@subcategoryId", SqlDbType.Int).Value = 2;

using SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
    Console.WriteLine(
        $"{reader.GetInt32(0)} | {reader.GetString(1)} | {reader.GetDecimal(2):C}");
}

關於參數類型、大小、空值與方向,請參見 「設定參數」。 不要將不受信任的值串接到 CommandText 中。

執行預存程序

設定 CommandType.StoredProcedure 並使用結構限定的程序名稱:

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)}");
}

Microsoft.Data.SqlClient 支援 CommandType.Text 和 CommandType.StoredProcedure。 設定 CommandType.TableDirect 會擲回 ArgumentOutOfRangeException。

檢查每個方法會回傳什麼

當你需要一個值時,請使用 ExecuteScalar:

using var command = new SqlCommand(
    "SELECT COUNT(*) FROM Production.Product WHERE SellEndDate IS NULL;",
    connection);

int currentProductCount = (int)command.ExecuteScalar()!;
Console.WriteLine($"Current products: {currentProductCount}");

ExecuteScalar 會在結果沒有資料列時回傳 DBNull.Value,並在第一個值是資料庫 null 時回傳 NULL。

使用 ExecuteNonQuery 來處理資料變更陳述式。 此範例回滾了變更,使 AdventureWorks 保持不變:

using SqlTransaction transaction = connection.BeginTransaction();

using var command = new SqlCommand("""
    UPDATE Sales.SalesOrderHeader
    SET Status = @status
    WHERE SalesOrderID = @salesOrderId;
    """, connection, transaction);
command.Parameters.Add("@status", SqlDbType.TinyInt).Value = 2;
command.Parameters.Add("@salesOrderId", SqlDbType.Int).Value = 43659;

int rowsAffected = command.ExecuteNonQuery();
Console.WriteLine($"Rows affected: {rowsAffected}");

transaction.Rollback();

SET NOCOUNT ON 抑制受影響資料列計數,因此 ExecuteNonQuery 即使語句變更資料,也能回傳 -1 。

使用ExecuteXmlReader以獲得FOR XML結果:

using var command = new SqlCommand("""
    SELECT TOP (3) ProductID, Name
    FROM Production.Product
    ORDER BY ProductID
    FOR XML PATH('Product'), ROOT('Products');
    """, connection);

using System.Xml.XmlReader reader = command.ExecuteXmlReader();
reader.MoveToContent();
Console.WriteLine(reader.ReadOuterXml());

使用本機交易

當連線有使用中的本機 SqlTransaction 時,將該交易指派給該連線上執行的每個指令。 此範例回滾了變更,使 AdventureWorks 保持不變:

using SqlTransaction transaction = connection.BeginTransaction();

using var command = new SqlCommand("""
    UPDATE Sales.SalesOrderHeader
    SET Status = @status
    WHERE SalesOrderID = @salesOrderId;
    """, connection, transaction);
command.Parameters.Add("@status", SqlDbType.TinyInt).Value = 2;
command.Parameters.Add("@salesOrderId", SqlDbType.Int).Value = 43659;

command.ExecuteNonQuery();
transaction.Rollback();

只有在應用程式接受完整交易時,才呼叫 Rollback 而非 Commit。 若指令不參考當前交易作業,則拋出 InvalidOperationException。 如果交易屬於另一個連線,執行也會失敗。 關於交易邊界與回滾,請參見 本地交易。

設定指令逾時

CommandTimeout 限制指令執行及累積網路讀取等待時間,以秒為單位。 如果已設定,指令會使用該連線的 Command Timeout 設定。 否則,預設時間是 30 秒。 值 0 沒有時間限制。 負值則拋出 ArgumentException。

當逾時到期時,指令會拋出 SqlException,並將 Number 設為 -2:

using var command = new SqlCommand(
    "WAITFOR DELAY '00:00:05';",
    connection)
{
    CommandTimeout = 1
};

try
{
    command.ExecuteNonQuery();
}
catch (SqlException exception) when (exception.Number == -2)
{
    Console.WriteLine("The command timed out.");
}

Connect Timeout 限制連線建立和連線池等待時間。 CommandTimeout 會在指令開始執行後開始。 欲了解更多資訊,請參閱 連線選項。

同時使用有限超時和取消權杖。 逾時設定會為資料庫作業設定時間上限。 取消令牌可以在逾時到期前將控制權還給呼叫者。 不要以為取消和超時會產生相同的例外。

重複使用及準備指令

先新增參數一次,然後每次執行結束後更改其值:

using var command = new SqlCommand("""
    SELECT TOP (1) Name, ListPrice
    FROM Production.Product
    WHERE ProductSubcategoryID = @subcategoryId
    ORDER BY ListPrice DESC;
    """, 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)}");
    }
}

只有當測量結果足以證明有此必要時,才呼叫 Prepare。 Prepare 不會準備沒有參數的儲存程序或文字指令。 對於參數化文字,請在呼叫 Scale 前,先對每個參數設置完整的中繼資料,例如 Prepare、Size 和 Precision。

疑難排解命令

在資料庫操作周圍捕捉 SqlException,當伺服器回傳多個錯誤時檢查其 Errors 集合。 關於指令錯誤、警告及資訊訊息,請參見 處理錯誤與訊息。

當錯誤需要更多上下文時,可以使用 診斷計數器、 事件來源追蹤和 SqlClient 故障排除指南 。 只有在重複指令安全時,才套用 可設定的重試邏輯 。