4단계: ADO.NET을 사용하여 탄력적으로 SQL에 연결

ADO.NET 다운로드

이 항목에서는 사용자 지정 다시 시도 논리를 보여 주는 C# 코드 샘플을 제공합니다. 다시 시도 논리는 안정성을 제공합니다. 재시도 논리는 프로그램이 잠시 기다렸다가 재시도하는 경우 없어지는 경향이 있는 임시 오류 또는 일시적 오류를 정상적으로 처리하도록 디자인되었습니다.

일시적인 오류의 원인은 다음과 같습니다.

  • 인터넷을 지원하는 네트워킹의 짧은 오류입니다.
  • 클라우드 시스템은 쿼리가 전송되는 순간 리소스의 부하를 분산시킬 수 있습니다.

로컬 Microsoft SQL Server에 연결하기 위한 ADO.NET 클래스를 통해 Azure SQL Database에도 연결할 수 있습니다. 그러나 ADO.NET 클래스는 프로덕션 사용에 필요한 모든 견고성과 안정성을 자체적으로 제공할 수 없습니다. 클라이언트 프로그램은 일시적 오류가 발생할 수 있으며, 이 오류는 자동으로, 정상적으로 복구되고 자체적으로 계속되어야 합니다.

1단계: 일시적인 오류 식별

프로그램은 일시적인 오류와 영구 오류를 구분해야 합니다. 일시적 오류는 일시적인 네트워크 문제와 같이 단기간에 정리될 수 있는 오류 조건입니다. 영구 오류의 예는 프로그램에 대상 데이터베이스 이름의 철자가 잘못된 경우가 있습니다. 이러한 경우 "이러한 데이터베이스를 찾을 수 없음" 오류가 지속되며 짧은 기간 내에 지울 수 없습니다.

일시적인 오류로 분류되는 오류 번호 목록은 SQL Database 클라이언트 응용 프로그램의 오류 메시지에서 사용할 수 있습니다.

2단계: 샘플 애플리케이션 만들기 및 실행

이 샘플은 .NET Framework 4.6.2 이상이 설치되어 있다고 가정합니다. C# 코드 샘플은 Program.cs 파일 하나로 구성됩니다. 해당 코드는 다음 섹션에서 제공됩니다.

2.a단계: 코드 샘플 캡처 및 컴파일

다음 샘플부터 샘플 컴파일을 시작할 수 있습니다.

  1. 무료 Visual Studio Community 버전에서 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에 맞춰 조정되었으며, tcp:의 네 글자 접두사가 포함되어 있기 때문입니다. 하지만 Microsoft 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);
                succeeded = false;
                break;
            }
            catch (TestSqlException sqlExc) {
                if (TransientErrorNumbers.Contains(sqlExc.Number))
                {
                    Console.WriteLine("{0}: transient occurred. (TESTING.)", sqlExc.Number);
                    continue;
                }

                Console.WriteLine(sqlExc);
                succeeded = false;
                break;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                succeeded = false;
                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>
    /// You must edit the four 'my' string values.
    /// </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             = "MyPassword",
            // 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단계: 테스트 예외 Throw

샘플 코드에는 다음 항목이 포함됩니다.

  • 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. 편집하며 계속하기 기능을 사용하여 아래 두 줄에 의도적으로 서버 이름을 잘못 입력합니다.
    • 프로그램을 실행하고 중단점으로 돌아옵니다.
    • 오류 40615가 발생합니다.
  4. 맞춤법 오류를 수정합니다.
  5. 프로그램을 실행하고 성공적으로 완료하도록 합니다.
  6. 40615를 제거하고 다시 컴파일합니다.

다음 단계

다른 모범 사례 및 디자인 지침을 살펴보려면 SQL Database: 링크, 모범 사례 및 디자인 지침 커넥트 연결에 방문하세요.