使用 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 故障排除指南 。 只有在重複指令安全時,才套用 可設定的重試邏輯 。