手順 4:ADO.NET で SQL に弾性的に接続する

ADO.NET のダウンロード

この記事では、手動実装されたリトライロジックを示すC#コードのサンプルを提供しています。 リトライロジックは、プログラムが待って再試行すると消える可能性のある一時的な エラーや一時的な故障を処理します。

Important

Microsoftを使う新しいアプリケーション向け。Data.SqlClient 3.0以降のバージョンでは、リトライループの実装ではなく、ドライバーの設定可能な再試行ロジックを優先します。 まずは SqlClientのリトライロジックの設定から始めます。

一時的な障害の原因には、次のものがあります。

  • インターネットを支えるネットワークの一時的な故障。
  • クラウドシステムは、あなたのクエリが届いたときにリソースの負荷分散をしているかもしれません。

SQL Serverに接続するためのADO.NETクラスはAzure SQL Databaseにも接続可能です。 クライアントプログラムは一時的な故障に遭遇することがあります。 操作に適した有界リトライポリシーを用いて回復するはずです。

手順 1:一時的なエラーを識別する

プログラムは一時的なエラーと持続的なエラーを区別しなければなりません。 一時的エラーとは、トランジェントネットワークの問題のように短時間で解消される可能性のあるエラー状態のことです。 持続的なエラーの例としては、ターゲットデータベース名の綴りミスがあります。 その場合、「そのようなデータベースが見つかりません」というエラーは続き、すぐには解消されません。

一時的故障として分類されたエラー番号のリストは、 SQLデータベースクライアントアプリケーションのエラーメッセージで入手可能です。

手順 2:サンプル アプリケーションを作成して実行する

このサンプルは、.NET Framework 4.6.2 以降がインストールされていることを前提としています。 C#コードのサンプルは、 Program.csという名前の1つのファイルで構成されています。 次のセクションでコードについて説明します。

手順 2.a: サンプル コードをキャプチャしてコンパイルする

次の手順で、サンプルをコンパイルができます。

  1. 無料の Visual Studio Community edition、C# コンソール アプリケーション テンプレートから新しいプロジェクトを作成します。

    • [ファイル]>[新規]>[プロジェクト]>[インストール済み]>[テンプレート]>[Visual C#]>[Windows]>[クラシック デスクトップ]>[コンソール アプリケーション] を選択します。

    • プロジェクトに RetryAdo2 という名前を付けます。

  2. [ソリューション エクスプローラー] ペインを開きます。

    • プロジェクトの名前を確認します。
    • プロジェクトで、Microsoft.Data.SqlClient パッケージに NuGet の依存関係を追加します。
    • Program.csファイル名を表示してください。
  3. Program.cs ファイルを開きます。

  4. Program.csファイルの全内容を次のコードブロックのコードに置き換えます。

  5. ビルド>ビルドソリューション」のメニューを選択してください。

手順 2.b: サンプル コードをコピーして貼り付ける

このコードを Program.cs ファイルに貼り付けてください。

その後、サーバー名とパスワードの文字列を編集します。 これらの文字列は「 GetSqlConnectionString」というメソッドで見つけることができます。

この接続文字列はAzure SQL Database向けに作られており、4文字の接頭辞がtcp:含まれています。 ただし、サーバーの文字列を調整してSQL Serverインスタンスに接続することは可能です。

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

namespace RetryAdo2;

public class Program
{
    public static int Main(string[] args)
    {
        bool succeeded = false;
        const int totalNumberOfTimesToTry = 4;
        int retryIntervalSeconds    = 10;

        for (int tries = 1; tries <= totalNumberOfTimesToTry; tries++)
        {
            try
            {
                if (tries > 1)
                {
                    Console.WriteLine(
                        "Transient error encountered. Will begin attempt number {0} of {1} max...",
                        tries,
                        totalNumberOfTimesToTry
                    );
                    Thread.Sleep(1000 * retryIntervalSeconds);
                    retryIntervalSeconds = Convert.ToInt32(retryIntervalSeconds * 1.5);
                }
                AccessDatabase();
                succeeded = true;
                break;
            }
            catch (SqlException sqlExc) {
                if (TransientErrorNumbers.Contains(sqlExc.Number))
                {
                    Console.WriteLine("{0}: transient occurred.", sqlExc.Number);
                    continue;
                }

                Console.WriteLine(sqlExc);
                break;
            }
            catch (TestSqlException sqlExc) {
                if (TransientErrorNumbers.Contains(sqlExc.Number))
                {
                    Console.WriteLine("{0}: transient occurred. (TESTING.)", sqlExc.Number);
                    continue;
                }

                Console.WriteLine(sqlExc);
                break;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                break;
            }
        }

        if (!succeeded) {
            Console.WriteLine("ERROR: Unable to access the database!");
            return 1;
        }

        return 0;
    }

    /// <summary>
    /// Connects to the database, reads,
    /// prints results to the console.
    /// </summary>
    static void AccessDatabase() {
        //throw new TestSqlException(4060); //(7654321);  // Uncomment for testing.

        using var sqlConnection = new SqlConnection(GetSqlConnectionString());

        using var dbCommand = sqlConnection.CreateCommand();

        dbCommand.CommandText = @"
        SELECT TOP 3
            ob.name,
            CAST(ob.object_id as NVARCHAR(32)) AS [object_id]
        FROM sys.objects AS ob
        WHERE ob.type='IT'
        ORDER BY ob.name;";
        
        sqlConnection.Open();
        var dataReader = dbCommand.ExecuteReader();

        while (dataReader.Read())
        {
            Console.WriteLine(
                "{0}\t{1}",
                dataReader.GetString(0),
                dataReader.GetString(1)
            );
        }
    }

    /// <summary>
    /// Edit the four string values in accordance with your environment.
    /// </summary>
    /// <returns>An ADO.NET connection string.</returns>
    static private string GetSqlConnectionString()
    {
        // Prepare the connection string to Azure SQL Database.
        var sqlConnectionSB = new SqlConnectionStringBuilder
        {
            // Change these values to your values.
            DataSource           = "tcp:myazuresqldbserver.database.windows.net,1433", //["Server"]
            InitialCatalog       = "MyDatabase",                                       //["Database"]
            UserID               = "MyLogin",                                          // "@yourservername"  as suffix sometimes.
            Password             = "<password>",
            // Adjust these values if you like. (ADO.NET 4.5.1 or later.)
            ConnectRetryCount    = 3,
            ConnectRetryInterval = 10, // Seconds.
            // Leave these values as they are.
            IntegratedSecurity = false,
            Encrypt            = true,
            ConnectTimeout     = 30
        };

        return sqlConnectionSB.ToString();
    }

    static List<int> TransientErrorNumbers = new()
    {
        4060, 40197, 40501, 40613, 49918, 49919, 49920, 11001
    };
}

/// <summary>
/// For testing retry logic, you can have method
/// AccessDatabase start by throwing a new
/// TestSqlException with a Number that does
/// or does not match a transient error number
/// present in TransientErrorNumbers.
/// </summary>
internal class TestSqlException : ApplicationException
{
    internal TestSqlException(int testErrorNumber)
    {
        Number = testErrorNumber;
    }

    internal int Number { get; set; }
}

手順 2.c: プログラムの実行

RetryAdo2.exe実行ファイルはパラメータを受け取りません。 .exeを動かすには:

  1. RetryAdo2.exeバイナリをコンパイルしたフォルダのコンソールウィンドウを開きます。
  2. 入力パラメータなしで RetryAdo2.exe 実行します。
database_firewall_rules_table   245575913
filestream_tombstone_2073058421 2073058421
filetable_updates_2105058535    2105058535

手順 3:再試行ロジックをテストする方法

一時的なエラーをシミュレートしてリトライロジックをテストする方法は複数あります。

手順 3.a: テスト例外を発生させる

コード サンプルには、以下のものが含まれています。

  • Numberという性質を持つ小さな第二クラスTestSqlException
  • //throw new TestSqlException(4060); のコメントを解除できます。

throw文のコメントを解除して再コンパイルすると、次の RetryAdo2.exe 実行は次のような結果を出します。

[C:\VS15\RetryAdo2\RetryAdo2\bin\Debug\]
>> RetryAdo2.exe
4060: transient occurred. (TESTING.)
Transient error encountered. Will begin attempt number 2 of 4 max...
4060: transient occurred. (TESTING.)
Transient error encountered. Will begin attempt number 3 of 4 max...
4060: transient occurred. (TESTING.)
Transient error encountered. Will begin attempt number 4 of 4 max...
4060: transient occurred. (TESTING.)
ERROR: Unable to access the database!

[C:\VS15\RetryAdo2\RetryAdo2\bin\Debug\]
>>

手順 3.b: 永続的なエラーで再テストする

永続的エラーを正しく処理することを証明するために、前のテストを再度実行します。 実際の過渡的な誤り番号(例えば4060)を使う代わりに、架空の数字7654321を使いましょう。 プログラムはこの数値を永続的なエラーとして扱い、再試行を回避します。

手順 3.c: ネットワークから切断する

  1. クライアント コンピューターをネットワークから切断します。
    • デスクトップの場合、ネットワーク ケーブルを外します。
    • ラップトップの場合、ファンクション キーの組み合わせを押してネットワーク アダプターをオフにします。
  2. RetryAdo2.exeを始めて、コンソールが最初の一時的なエラー(おそらく11001)を表示するのを待ちます。
  3. RetryAdo2.exeは引き続き稼働している間にネットワークに再接続してください。
  4. 続いて行われる再試行が成功したことをコンソールで確認します。

手順 3.d: サーバー名のスペルを一時的に間違える

  1. 一時的に40615を別のエラー番号として TransientErrorNumbersに追加し、再コンパイルします。
  2. new QC.SqlConnectionStringBuilder()の行にブレークポイントを設定します。
  3. Edit and Continue 機能を使って、この少し下の行で意図的にサーバー名のスペルを間違えてください。
    • プログラムの実行を続け、設定したブレークポイントに戻るまで待ちます。
    • 40615 エラーが発生します。
  4. スペルミスを修正します。
  5. プログラムを実行すると正常に終了します。
  6. 40615 を削除し、再コンパイルします。

次のステップ