使用 SqlBatch 執行多個指令

用 SqlBatch 來將一組 SqlBatchCommand 有序的物件集合傳送到一個開的 SqlConnection。 每個批次指令都有自己的指令文字、命令類型、參數、指令行為及受影響的資料列數結果。

當應用程式有明確區分的指令要一起傳送時,請使用 SqlBatch 當工作只有單一陳述式或儲存程序時,可以使用一個參數化 SqlCommand。 對於大型列集,可以使用 SqlBulkCopy 或使用表值參數。

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

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

版本與平台支援

這些範例針對 Microsoft.Data.SqlClient 7.1.0。 選擇你的應用程式適用且受 Microsoft.Data.SqlClient support lifecycle 支援的套件版本與目標架構。

在現代 .NET 上,SqlBatch衍生自DbBatch。 Microsoft.Data.SqlClient 也在其 .NET Framework 與 .NET Standard 2.0 套件資產中提供 SqlBatch。 公開 API 介面會因目標框架而異,因此請為您發佈的每個目標編譯應用程式。

建立批次

建立一個開連線的批次,然後依執行順序新增指令:

using var batch = new SqlBatch(connection);
batch.BatchCommands.Add(new SqlBatchCommand("""
    SELECT TOP (3) ProductID, Name
    FROM Production.Product
    ORDER BY ProductID;
    """));
batch.BatchCommands.Add(new SqlBatchCommand("""
    SELECT TOP (3) SalesOrderID, OrderDate
    FROM Sales.SalesOrderHeader
    ORDER BY SalesOrderID;
    """));

using DbDataReader reader = batch.ExecuteReader();
do
{
    while (reader.Read())
    {
        Console.WriteLine($"{reader.GetValue(0)} | {reader.GetValue(1)}");
    }
}
while (reader.NextResult());

CommandType.Text 是預設值。 將儲存程序的批次指令設為 CommandType.StoredProcedure。 設定 CommandType.TableDirect 會擲回 ArgumentOutOfRangeException。

為批次指令新增參數

參數屬於個別項目 SqlBatchCommand。 兩個批次指令中的相同佔位名稱不會共享值:

using var batch = new SqlBatch(connection);

var products = new SqlBatchCommand("""
    SELECT ProductID, Name
    FROM Production.Product
    WHERE ProductSubcategoryID = @id
    ORDER BY ProductID;
    """);
products.Parameters.Add("@id", SqlDbType.Int).Value = 2;
batch.BatchCommands.Add(products);

var orders = new SqlBatchCommand("""
    SELECT SalesOrderID, OrderDate
    FROM Sales.SalesOrderHeader
    WHERE SalesPersonID = @id
    ORDER BY SalesOrderID;
    """);
orders.Parameters.Add("@id", SqlDbType.Int).Value = 279;
batch.BatchCommands.Add(orders);

using DbDataReader reader = batch.ExecuteReader();

設定每個參數的 SQL 類型,以及資料庫定義中適用的大小、精確度和小數位數。 如需相關資訊,請參閱設定參數。

執行資料變更指令

當批次變更資料且不需要結果列時,請使用 ExecuteNonQuery 或 ExecuteNonQueryAsync。 回傳的值是當有列數可用時受影響的總列數。 執行後,每個指令會 RecordsAffected 報告自己的計數。

以下範例回滾其變更:

using SqlTransaction transaction = connection.BeginTransaction();
using var batch = new SqlBatch(connection, transaction);

var firstUpdate = new SqlBatchCommand("""
    UPDATE Sales.SalesOrderHeader
    SET Status = @status
    WHERE SalesOrderID = @salesOrderId;
    """);
firstUpdate.Parameters.Add("@status", SqlDbType.TinyInt).Value = 2;
firstUpdate.Parameters.Add("@salesOrderId", SqlDbType.Int).Value = 43659;
batch.BatchCommands.Add(firstUpdate);

var secondUpdate = new SqlBatchCommand("""
    UPDATE Sales.SalesOrderHeader
    SET Status = @status
    WHERE SalesOrderID = @salesOrderId;
    """);
secondUpdate.Parameters.Add("@status", SqlDbType.TinyInt).Value = 2;
secondUpdate.Parameters.Add("@salesOrderId", SqlDbType.Int).Value = 43660;
batch.BatchCommands.Add(secondUpdate);

int totalRows = batch.ExecuteNonQuery();
Console.WriteLine($"Total rows: {totalRows}");

foreach (SqlBatchCommand command in batch.BatchCommands)
{
    Console.WriteLine(command.RecordsAffected);
}

transaction.Rollback();

查看多個結果

當任何指令傳回資料列時,請使用 ExecuteReader 或 ExecuteReaderAsync。 讀卡機會以批次指令順序輸出結果。 呼叫NextResult或NextResultAsync,直到它傳回false。

指令可以產生任何列集、單一列集,或多列集。 不要以為一個 NextResult 呼叫對應一個 SqlBatchCommand。

使用命令行為

當讀取器需要順序存取或必須關閉連線時,將批次層級 CommandBehavior 傳遞至 ExecuteReader 或 ExecuteReaderAsync:

using var batch = new SqlBatch(connection);
batch.BatchCommands.Add(new SqlBatchCommand("""
    SELECT DocumentSummary
    FROM Production.Document
    WHERE DocumentSummary IS NOT NULL;
    """));

using DbDataReader reader =
    batch.ExecuteReader(CommandBehavior.SequentialAccess);

char[] buffer = new char[4096];
while (reader.Read())
{
    using TextReader summary = reader.GetTextReader(0);
    int count;
    while ((count = summary.Read(buffer, 0, buffer.Length)) > 0)
    {
        Console.Out.Write(buffer, 0, count);
    }
    Console.WriteLine();
}

批次層級的多載僅接受 CommandBehavior.Default、CommandBehavior.SequentialAccess、CommandBehavior.CloseConnection及這些值的組合。 其他有效值,如 SingleRow、SchemaOnly 和 KeyInfo,在傳遞給批次層級多載版本時會拋出 ArgumentOutOfRangeException。 批次層級的行為多載自 Microsoft.Data.SqlClient 7.1 起可用。

在個別指令上設定 CommandBehavior,以選擇如 SingleRow、SchemaOnly 和 KeyInfo 等選項。 使用批次層級的多載 和 SequentialAccess 和 CloseConnection。

使用事務

SqlBatch 不會使其命令具備原子性。 當所有指令都必須一起認可或回復時,請使用明確的交易作業。 此範例回滾,使 AdventureWorks 保持不變:

using SqlTransaction transaction = connection.BeginTransaction();
using var batch = new SqlBatch(connection, transaction);

batch.BatchCommands.Add(new SqlBatchCommand("""
    UPDATE Sales.SalesOrderHeader
    SET Status = 2
    WHERE SalesOrderID = 43659;
    """));
batch.BatchCommands.Add(new SqlBatchCommand("""
    UPDATE Sales.SalesOrderHeader
    SET Status = 2
    WHERE SalesOrderID = 43660;
    """));

batch.ExecuteNonQuery();
transaction.Rollback();

只有在應用程式接受完整批次時,才呼叫Rollback而非Commit。 在執行前指派事務。 交易與批次必須使用相同的連線。 SqlClient 未覆寫非同步交易方法,因此 BeginTransactionAsync、CommitAsync 和 RollbackAsync 會以同步方式完成。 欲了解更多資訊,請參閱 使用基於任務的 SQL 事務方法。

處理逾時、取消與錯誤

將批次的 Timeout 設定為幾秒。 批次組在您設定連線後,會使用該連線的 Command Timeout 設定。 否則,預設時間是 30 秒。 值 0 沒有時間限制,負值則擲出 ArgumentException。 當呼叫者需要停止等待或呼叫 Cancel時,將取消標記傳遞給非同步執行方法。 取消僅保證盡力處理。 作業可能在取消生效前完成。

執行失敗時,捕捉 SqlException 並檢查其 Errors 集合中的每一項。 BatchCommand 會在可取得該資訊時識別相關的 SqlBatchCommand:

using var batch = new SqlBatch(connection);
batch.BatchCommands.Add(new SqlBatchCommand(
    "SELECT TOP (1) ProductID FROM Production.Product;"));
batch.BatchCommands.Add(new SqlBatchCommand("SELECT 1 / 0;"));

try
{
    using DbDataReader reader = batch.ExecuteReader();
    do
    {
        while (reader.Read())
        {
            Console.WriteLine(reader.GetValue(0));
        }
    }
    while (reader.NextResult());
}
catch (SqlException exception)
{
    Console.WriteLine(
        exception.BatchCommand is null
            ? "The batch failed."
            : $"Failed command: {exception.BatchCommand.CommandText}");
}

Reader 的執行是漸進式的。 當 Read 或 NextResult 前進到該指令時,較後的批次指令可能會出現錯誤,因此請在 try 區塊內處理讀取器。

不要記錄包含憑證、權杖或敏感資料的參數值。 關於錯誤欄位與資訊訊息,請參見 處理錯誤與訊息。