SqlBatchで複数のコマンドを実行する

SqlBatchを使って、順序付けられたSqlBatchCommandオブジェクトの集合を1つの開いたSqlConnectionに送ります。 各バッチコマンドは独自のコマンドテキスト、コマンドタイプ、パラメータ、コマンドの挙動、そして影響を受けた行数の結果を持っています。

アプリケーションがそれぞれ異なるコマンドを持っていて、それを一緒に送りたい場合に SqlBatch 使う。 作業が1つの文やストアドプロシージャの場合は、パラメータ付き SqlCommand を1つ使います。 大きな行セットには 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 サポート ライフサイクルでサポートされているパッケージ バージョンとターゲット フレームワークを選択してください。

SqlBatch は現代の .NET では 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に属します。 2つのバッチコマンドで同じプレースホルダー名でも、値は共有されません:

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 を返すまで続けてください。

コマンドは行セットを生成しない場合もあれば、1つまたは複数の行セットを生成する場合もあります。 1回のNextResult呼び出しが1つの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 以降で利用可能です。

SchemaOnly、KeyInfo、CommandBehavior などのオプションでは、個別のコマンドに SingleRow を設定しましょう。 バッチレベルのオーバーロードを 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}");
}

リーダーの処理は段階的です。 後のバッチコマンドからのエラーは、Read または NextResult がそのコマンドに進む際に表面化する可能性があるため、try ブロック内でリーダーを処理してください。

認証情報、トークン、機密データを含むパラメータ値は記録しないでください。 エラーフィールドや情報メッセージについては「エラーとメッセージの処理」を参照してください。