コマンドを実行する

SqlCommandを使って Transact-SQL(T-SQL)文やストアドプロシージャを実行します。 コマンドを実行するには、SqlConnection を開いている必要があります。 使用後はコマンドを破棄し、接続を再利用する前にリーダー オブジェクトを破棄してください。

この記事の例では、connection という名前のオープン SqlConnection が AdventureWorks サンプル データベース をターゲットとしていることを前提としています。

C#の例は using Microsoft.Data.SqlClient;を必要とします。 SqlDbType、CommandType、CommandBehaviorを使う例もまた、using System.Data;が必要です。

接続の設定、認証、暗号化については、接続文字列および暗号化および証明書の検証を参照してください。

実行方法を選択する

期待される結果に合致する方法を選択してください:

メソッド 次の場合に使用します。 結果
ExecuteNonQuery コマンドはデータやスキーマを変更します。 影響を受ける行の数。 -1 は、カウントが存在しない場合(SET NOCOUNT ON がカウントを抑制する場合を含む)に発生することがあります。
ExecuteScalar 必要なのは一つの値です。 1 行目の 1 列目、または結果に行がない場合は null。 データベース NULL が Valueとして返されます。
ExecuteReader 行、複数の結果セット、またはスキーマが必要です。 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}");
}

パラメータの種類、サイズ、null値、方向については「パラメータの設定」を参照してください。 信頼できない値を 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 がスローされます。

各メソッドが何を返すか確認してください

1つの値が必要な場合は 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は、結果に行がない場合はnullを返し、最初の値がデータベースDBNull.Valueの場合は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();

アプリケーションがトランザクション全体を受け入れる場合にのみ、CommitではなくRollbackを呼び出してください。 アクティブなトランザクションを参照しないコマンドは 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 のトラブルシューティング ガイド を活用してください。 コマンドを繰り返しても安全な場合にのみ 設定可能なリトライロジック を適用してください。