Microsoftのデータソースに接続してください。Data.SqlClient

SqlConnection は、SQL Server、Azure SQL Database、Azure SQL Managed Instance、または Microsoft Fabric の SQL データベースへの論理的な接続を 1 つ表します。 オブジェクトを開くと、接続プールから物理的な接続が得られます。 閉じたり処分したりすると、その物理的な接続がプールに戻されます。

作業単位には短命の SqlConnection オブジェクトを使いましょう。 アプリケーション用にグローバル接続を一つだけ開いておかないでほしいです。

Prerequisites

  • .NET 10 SDKまたはそれ以降でサポートされている .NET SDKです。 SDKはWindows、Linux、macOSで動作します。 また、各サンプルの.NET Framework版も構築しており、これはWindowsでのみ動作し、.NET Framework 4.8.1が必要です。
  • SQL Server、Azure SQL Database、Azure SQL Managed Instance、またはMicrosoft FabricのSQLデータベースに接続できるデータベースです。

プロジェクトを作成する

Microsoft。Data.SqlClient 7.0以降のバージョンでは、Microsoft.Data.SqlClient.Extensions.Azureなどのドライバー提供のMicrosoft Entra ID認証モードのために、バージョンが一致するActive Directory Defaultパッケージが必要です。 もしアプリがSQL認証やWindows統合認証のみを使用している場合は、省略しても構いません。

空のフォルダで、以下のコマンドを実行してください:

dotnet new console --framework net10.0 --name SqlClientConnect
cd SqlClientConnect
dotnet add package Microsoft.Data.SqlClient --version 7.1.1
dotnet add package Microsoft.Data.SqlClient.Extensions.Azure --version 7.1.1

この記事のサンプルでは、SQL_CONNECTION_STRING 環境変数から接続文字列を読み取ります。 サンプルを取る前に設定してください。 接続文字列 フォーマットについては「Connection strings」を参照してください。

トランザクションサンプルを除くすべてのサンプルは完全なプログラムです。 その1つを実行するには、Program.cs の内容をそれに置き換えてから、dotnet runを実行してください。 トランザクションサンプルは独自のメソッド用の一部です。 .NETタブはトップレベルの文、暗黙のusing指令、および暗黙のawait usingを使用します。 .NET FrameworkタブはC# 7.3を使っていますが、await usingがないためusing文を使っています。

接続を開けてください

非同期コードで OpenAsync を呼び出すか、同期コードで Open を呼び出してください。 await usingまたはusingで接続を宣言し、例外が発生した場合でもスコープを抜けるときに接続が破棄されるようにします。

Windows の統合認証では、接続はプロセスの資格情報を使用します。 ウェブアプリケーションやWindowsサービスの場合、それはサインインユーザーではなくアプリケーションプールやサービスアカウントです。 詳細については、「 認証パターンの選択」をご覧ください。

この完全なプログラムは接続を開き、各状態変化を報告し、2つのサーバープロパティを読み込みます。

using Microsoft.Data.SqlClient;

string connectionString =
    Environment.GetEnvironmentVariable("SQL_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_CONNECTION_STRING environment variable.");

await using var connection = new SqlConnection(connectionString);
connection.StateChange += (_, e) =>
    Console.WriteLine($"State: {e.OriginalState} to {e.CurrentState}");

await connection.OpenAsync();

await using var command = new SqlCommand(
    """
    SELECT CAST(SERVERPROPERTY('Edition') AS nvarchar(128)),
           CAST(SERVERPROPERTY('EngineEdition') AS int);
    """,
    connection);

await using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
    await reader.ReadAsync();
    Console.WriteLine($"Edition: {reader.GetString(0)}");
    Console.WriteLine($"EngineEdition: {reader.GetInt32(1)}");
}

SQL Server 2025 Enterpriseエディションに対して、出力は次の通りです:

State: Closed to Open
Edition: Enterprise Edition: Core-based Licensing (64-bit)
EngineEdition: 3
State: Open to Closed

最後の行は、プログラムの最後に接続が破棄されるときに現れます。

接続のクローズと解放

接続を終えたら必ず接続を閉じて、物理的な接続がプールに戻るようにしてください。 using文とawait using文は、未処理の例外がスローされた場合も含め、コードがブロックを終了した際に接続を破棄します。 CloseやDisposeに直接電話することもできます。 Close と Dispose は物理的な接続に同じ効果をもたらします。

次の規則に従います。

  • リーダーやコマンドは、それらを所有している接続より先に破棄してください。
  • 接続をプールに返すためにガベージコレクションに頼らないでください。 閉じずにスコープを外れた接続はすぐには返されず、接続プールの接続が不足する可能性があります。
  • クラスフィニライザー内で、接続やリーダー、その他の管理オブジェクトに対して Close や Dispose を呼び出さないでください。 ファイナライザーは、クラスが直接所有する管理されていないリソースのみを解放すべきです。 詳細は「ガベージ コレクション」をご覧ください。
  • Closeの後、同じオブジェクトで再びOpenを呼び出せます。 Disposeした後、新しいオブジェクトを作成します。

プーリングが有効の場合、接続を閉じるとネットワーク接続を閉じずにプールに戻されます。 接続が再利用されると、プールはセッション状態をリセットします。 詳細については、SQL Server connection poolingをご覧ください。

非同期 API の使用

ウェブサーバー、サービス、ユーザーインターフェース、ワーカーでのネットワークバウンドデータベース作業には非同期呼び出しを使用する:

  • OpenAsync(cancellationToken)
  • ExecuteNonQueryAsync(cancellationToken)
  • ExecuteReaderAsync(cancellationToken)
  • ExecuteScalarAsync(cancellationToken)
  • ReadAsync(cancellationToken)

現在の非同期操作が完了する前に、接続、コマンド、リーダーの操作を始めないでください。

キャンセルとタイムアウトを適用してください

呼び出し元の CancellationToken を、すべての非同期データベース呼び出しに渡してください。 キャンセルはドライバーに保留中の作業の停止を求めるものですが、即時完了が保証されるわけではありません。 制限付き接続とコマンドのタイムアウトを使い続けてください。

これらの制御には別々のスコープがあります:

制御 Scope
Connect Timeout 接続確立、またはプールされた接続を待機すること
SqlCommand.CommandTimeout 1回のコマンド実行
CancellationToken 発信者からの非同期操作のキャンセルリクエスト

タイムアウトやキャンセルはサーバーが操作をロールバックしたことを証明しません。 複数の変更を一つの単位としてコミットまたはロールバックする必要があるトランザクションを使い、その操作の冪等性やトランザクション結果から再試行の判断を行います。

トラック接続状態

StateプロパティはConnectionState列挙からスナップショットを返します。

State Meaning
Closed 論理接続が開かれていません。
Connecting 公開作戦が進行中です。
Open 論理的なつながりは開かれています。

状態が変化したときに StateChange イベントが発生します。 その StateChangeEventArgs 引数には二つの性質があります。

  • OriginalState は変化前の状態です。
  • CurrentState は変化後の状態です。

ドライバーはClosed から Open への遷移と、Open から Closed への遷移を報告します。これはOpen a connectionのサンプルが示す通りです。 Executing、Fetching、Brokenをアプリケーションのライフサイクル段階として観察することに頼らないでください。

コマンドの前に State を健康チェックとして使わないでください。 ネットワークはチェック後に故障する可能性があります。 操作を実行し、その結果発生する例外を処理します。 StateChangeは診断用に使い、並行作業の調整には使いません。

お知らせメッセージを受信する

InfoMessageイベントでは、サーバーがエラーを発生させることなく送信するメッセージが配信されます。 これらのメッセージには、PRINT 出力、重大度が10以下の RAISERROR メッセージ、サーバー警告が含まれます。 重大度が11以上のエラーでは、代わりに SqlException が発生します。 重大度レベルについては、データベース エンジンエラーの重大度を参照してください。

イベントハンドラは SqlInfoMessageEventArgs オブジェクトを受け取ります。 その Errors プロパティは SqlError オブジェクトの集合を保持します。 それぞれにメッセージ番号、テキスト、重大度クラス、プロシージャ、行番号が含まれています。

using Microsoft.Data.SqlClient;

string connectionString =
    Environment.GetEnvironmentVariable("SQL_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_CONNECTION_STRING environment variable.");

await using var connection = new SqlConnection(connectionString);
connection.InfoMessage += (_, e) =>
{
    foreach (SqlError error in e.Errors)
    {
        Console.WriteLine(
            $"Message {error.Number}, severity {error.Class}: {error.Message}");
    }
};

await connection.OpenAsync();

await using var command = new SqlCommand(
    """
    PRINT 'Starting the batch.';
    RAISERROR('Processed %d rows.', 10, 1, 42);
    """,
    connection);

await command.ExecuteNonQueryAsync();

出力は次のとおりです。

Message 0, severity 0: Starting the batch.
Message 50000, severity 0: Processed 42 rows.

サーバーはクラス0として10の RAISERROR 重大度を報告しているので、 SqlError.Class 両方のメッセージで 0 を示しています。

ユーザーエラーを情報メッセージとして扱う

デフォルトでは、ドライバーは重大度が 11 以上のエラーを SqlException をスローすることで報告します。 その例外はサーバーがバッチを止めたという意味ではありません。 多くのエラー、例えば重大度 16 の RAISERROR では、サーバーが残りのステートメントを実行できるため、例外を検出しても後のステートメントでデータが変更されることがあります。 これらのエラーを例外ではなくメッセージとして受け取るには、 FireInfoMessageEventOnUserErrors を trueに設定してください。 その後、ドライバーは例外をスローする代わりに、重大度が16以下のエラーを InfoMessage イベントに送ります。 このプロパティはドライバーのエラー報告方法を変えます。 サーバーが実行するステートメントは変わりません。

using Microsoft.Data.SqlClient;

string connectionString =
    Environment.GetEnvironmentVariable("SQL_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_CONNECTION_STRING environment variable.");

await using var connection = new SqlConnection(connectionString)
{
    FireInfoMessageEventOnUserErrors = true,
};
connection.InfoMessage += (_, e) =>
{
    foreach (SqlError error in e.Errors)
    {
        Console.WriteLine(
            $"Message {error.Number}, severity {error.Class}: {error.Message}");
    }
};

await connection.OpenAsync();

await using var command = new SqlCommand(
    """
    RAISERROR('Row 7 failed validation.', 16, 1);
    SELECT 'The batch continued.';
    """,
    connection);

await using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
    do
    {
        while (await reader.ReadAsync())
        {
            Console.WriteLine(reader.GetString(0));
        }
    }
    while (await reader.NextResultAsync());
}

出力は次のとおりです。

Message 50000, severity 16: Row 7 failed validation.
The batch continued.

エラーの重大度が17以上でも、この設定に関わらず SqlExceptionが発生します。 このプロパティを有効にすると、ハンドラーは障害を検出する責任があります。 エラーごとに例外を期待するコードでは、例外が発生しません。

アプリケーション ID を報告する

Microsoft.Data.SqlClient 7.1.0 以降では、フレームワーク、ツール、その他のミドルウェアは、どのアプリケーションが接続を開いたかを報告できます。 RegisteredApplication を RegisteredApplication 値に設定してから接続を開いてください。 ドライバーはログイン時に一度だけサーバーに値を送ります。

次の規則に従います。

  • プロパティで値を設定します。 それに対する 接続文字列 キーワードは存在しません。
  • 接続を開く前に値を設定します。 接続が開いている途中または開いている状態で設定すると、InvalidOperationException が発生します。
  • 値はテレメトリーとして扱いましょう。 クライアントは値を提供し、任意の識別子を報告できるため、サーバーはその識別子を検証しません。
  • プールされた接続は元のIDを報告します。 その値はプールキーの一部ではありません。 プールから提供される接続は、物理的な接続を作成させた論理接続の識別情報を報告します。

Caution

RegisteredApplicationを認可やその他のセキュリティ判断に使わないでください。 どのクライアントでも任意の識別子を報告できます。

識別子を選択してください

デフォルト値は Unknownで、 0です。 列挙には基礎となる ushort の型があり、識別子は 0 から 0xFFFF までの範囲です。

範囲 Use
0x0001 から 0x7FFF まで Microsoft が定義したアプリケーション。 名前付き列挙メンバーはこの範囲を使用します。
0x8000 から 0xBFFF まで 小規模な使用に限定されています。
0xC000 から 0xFFFF まで 一般利用と開発者利用

名前が挙げられたメンバーは、 EntityFrameworkCore、 SemanticKernel、 ManagementStudio、 SqlManagementObjects、 DataTierApplicationFramework、 SqlToolsService、 AspNetCoreDistributedSqlServerCache、 EntityFramework、 AzureFunctionsSqlExtension、 OrleansAdoNet、 DurableTaskSqlServer、 SqlPackage、 DataApiBuilderです。 名前付きメンバーがない識別子を報告するには、その番号を RegisteredApplication にキャストします。

列挙がRegisteredApplicationを使うため、型とプロパティはCLS準拠ではありません。 C#コードはそれらを直接使えます。

識別子を設定する

このプログラムは開発者の範囲から識別子を報告し、接続が開かれた後にコードが値を変更すると何が起こるかを示します:

using Microsoft.Data.SqlClient;

string connectionString =
    Environment.GetEnvironmentVariable("SQL_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_CONNECTION_STRING environment variable.");

await using var connection = new SqlConnection(connectionString)
{
    RegisteredApplication = (RegisteredApplication)0xC001,
};

await connection.OpenAsync();
Console.WriteLine(
    $"Reported identifier: 0x{(ushort)connection.RegisteredApplication:X4}");

try
{
    connection.RegisteredApplication = RegisteredApplication.Unknown;
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}

出力は次のとおりです。

Reported identifier: 0xC001
InvalidOperationException: Not allowed to change the 'RegisteredApplication' property. The connection's current state is open.

プーリング効果を理解する

ドライバーは新しい物理接続を作成した場合にのみ識別子を報告します。 プールが有効の場合:

  • プールされた物理接続を再利用する接続は、物理接続を作成した接続の識別子を報告し、自身の識別子ではありません。
  • プールがUnknownレポートMin Pool Sizeを満たすためにバックグラウンドで開く物理的な接続。

1 つの接続文字列が複数の ID に対応する場合は、報告された値を概算値として扱います。 正確な帰属が必要な場合は、その接続文字列のプーリングを無効にし、まず追加の接続コストを測定してください。

SqlConnection.Clone は RegisteredApplication 値を新しい接続にコピーします。

同時に接続を共有しないようにしましょう

SqlConnection、 SqlCommand、 SqlDataReader、 SqlTransaction は複数のスレッドによる同時使用をサポートしていません。 各同時操作に独自の接続を与え、接続プーリングで物理接続を再利用できるようにします。

マルチプルアクティブ結果セット(MARS)は、サポートシナリオにおいて1つの接続で複数のアクティブバッチを許可します。 SqlClientオブジェクトをスレッドセーフにするわけではなく、セッションやトランザクションのルールを追加します。 特定の操作が必要でない限り、無効にしておくのが良いでしょう。

依存注入のシングルトンとしてオープン SqlConnection を登録しないでください。 接続文字列、不変オプションオブジェクト、または新しい接続を作成するファクトリーを登録します。

トランザクションは慎重に使用する

ローカルトランザクションはその接続に属します。 トランザクション内のすべてのコマンドはその接続を使用し、 Transaction プロパティを設定しなければなりません。

await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(cancellationToken);

await using SqlTransaction transaction =
    (SqlTransaction)await connection.BeginTransactionAsync(cancellationToken);

using var command = new SqlCommand(sql, connection, transaction);
command.Parameters.Add(
    new SqlParameter("@value", SqlDbType.Int) { Value = value });
await command.ExecuteNonQueryAsync(cancellationToken);

await transaction.CommitAsync(cancellationToken);

コミット前に操作が失敗した場合、トランザクションを破棄するとロールバックされます。 取引は短くしましょう。 データベースのトランザクションがロックを保持している間は、ネットワーク通話やユーザー操作、無関係な計算はしないでください。

System.Transactions.Transaction.Currentがアクティブ時は、OpenとOpenAsyncが自動的に自動的に入隊します。 操作がアンビエントトランザクションの外に留まる必要がある場合にのみ Enlist=false 設定します。

論理接続を1つ測定する

StatisticsEnabledをtrueに設定して、1つのSqlConnectionオブジェクトのドライバー統計を収集します:

using System.Collections;
using Microsoft.Data.SqlClient;

string connectionString =
    Environment.GetEnvironmentVariable("SQL_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "Set the SQL_CONNECTION_STRING environment variable.");

await using var connection = new SqlConnection(connectionString)
{
    StatisticsEnabled = true,
};

await connection.OpenAsync();
connection.ResetStatistics();

await using var command = new SqlCommand("SELECT name FROM sys.databases;", connection);
await using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
    while (await reader.ReadAsync())
    {
    }
}

IDictionary statistics = connection.RetrieveStatistics();
Console.WriteLine($"ServerRoundtrips: {statistics["ServerRoundtrips"]}");
Console.WriteLine($"SelectRows: {statistics["SelectRows"]}");
Console.WriteLine($"BytesReceived > 0: {(long)statistics["BytesReceived"]! > 0}");

出力は往復1回を示しています。 行数はサーバー上のデータベース数に依存します:

ServerRoundtrips: 1
SelectRows: 14
BytesReceived > 0: True

RetrieveStatistics スナップショットを返します。 ResetStatistics 新しい測定境界が始まります。 StatisticsEnabledをfalseに設定して収集を停止してください。 これまでに収集された値は引き続き利用可能です。 統計は接続オブジェクトごとに割り当てられ、オーバーヘッドが増えるため、すべての本番リクエストではなくターゲット診断に有効にしてください。

接続エラーを処理する

故障を記録、翻訳、または再試行できる境界線で SqlException を捉えましょう。 レコード:

  • Number
  • State
  • Class
  • ClientConnectionId
  • 操作名と設定されたサーバーおよびデータベース識別子

接続文字列、password、client secret、access tokenはログにしないでください。

切断された接続は処分しましょう。 プールは無効な物理接続を検出すると削除します。 認証情報、トークン、証明書、DNSターゲット、サーバーが変更された場合は、再試行前に設定を修正してください。

一時的な故障には限定リトライロジックを使用してください。 初期オープンリトライ、アイドル接続リカバリー、コマンドリトライは異なるメカニズムです。 詳細については、「設定可能な再試行ロジック」および「接続オプション」の接続回復オプションをご覧ください。