表題の通り、ユニットテストとかでコンテナ化した DB を使いたかったんですけど、Dockerfile も docker-compose.yml も書きたくなかったので、 C# でのやり方をまとめます。
必要なパッケージ
今回は、以下のパッケージを使います
コードの全文
コードの解説
NUnit の StartDockerContainer を指定したメソッドでコンテナの初期化をします。
また、最後に OneTimeTearDown を指定したメソッドでコンテナを削除します。
削除をしないと、次回テスト実行時にややこしいので、必ずここで削除します。
StartDockerContainer の最初の方で、既存のコンテナが存在していないかの確認と、存在する場合は削除するコードを書いています。 これは、デバッグ実行などで中途半端に動かして、最後にコンテナを削除し忘れていると、次回実行時にややこしいことになるので、 一度チェックを入れています。
StartDockerContainer の最後でちょっと Delay を入れていますが、これはコンテナの立ち上がりを待つための措置です。(後述の SQL Server の設定でリトライを入れているので、無くても大丈夫な気もします)
InitializeDatabaseAsync で SQL Server の初期設定を行っています。 ここで重要な点は、下記のコードです。
var retryOptions = new SqlRetryLogicOption { NumberOfTries = 5, DeltaTime = TimeSpan.FromSeconds(1), MaxTimeInterval = TimeSpan.FromSeconds(20), AuthorizedSqlCondition = delegate (string query) { if (Regex.IsMatch(query, "(SELECT)")) { return true; } else { return false; } }, // 10054 : SQL Server とのコネクション確立に失敗した場合に発生するのでリトライする(SQL Server が完全に立ち上がる前にコネクションを開こうとすると発生する) // 18456 : SQL Server のログインに失敗した場合に発生するのでリトライ(10054 と同じく、立ち上がれば解消するはず) TransientErrors = new[] { 10054, 18456 } }; var retryLogicProvider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(retryOptions); retryLogicProvider.Retrying += (object? s, SqlRetryingEventArgs e) => { Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} : Retrying for {e.RetryCount} times for {e.Delay.TotalMilliseconds / 1000.0:0.00} sec - Error code: {(e.Exceptions[0] as SqlException).Number} - Error msg: {e.Exceptions[0].Message}"); };
コンテナの初期化時に少しディレイを入れていますが、立ち上がり時間は毎回まちまちなので、タイミングによっては立ち上がりが間に合わずエラーになる場合があります。 なので、特定のエラーコードに対してリトライ設定を入れます。
今回は、エラーコード 10054
と 18456
に対してリトライを設定します。
10054
は、SQL Server とのコネクション確立に失敗した場合に出るエラーです。
18456
は、SQL Server のログインに失敗した場合のエラーです。
SQL Server が立ち上がる前に接続を開く → 10054
が発生
SQL Server が立ち上がったが、ログイン出来る状態になっていない → 18456
が発生
となるので、この二つをリトライすることで実行時のストレスがぐっと下がります。
まとめ
C# だけでコンテナの立ち上げからの SQL Server の接続が出来ました。
Visual Studio + C# だと、普通にコマンドを流すよりもデバッグがやりやすくて良いですね。