设置一个 C# 方法,在第一次失败后尝试重新连接到不同端口上的 SQL Server

Wam*_*ama 1 .net c# sql sql-server connection-string

我正在尝试设置一种方法,该方法将尝试首先在默认端口(1433)上连接到 SQL Server,然后在失败时在其他端口(例如 7777)上连接。

我想避免重建连接字符串并在失败时尝试再次连接,因为此方法将按照配置的时间间隔执行,并且我希望消除尽可能多的开销。

我尝试了以下方法(由ConnectionStrings提供)

public void EstablishConnection()
{
    string ConnectionString = "Data Source=127.0.0.1; Failover Partner=127.0.0.1,7777; Initial Catalog=foo;Connection Timeout = 3; Persist Security Info =True;User ID=<id>;Password=<password>";

    try
    {
        SqlConnection Connection = new SqlConnection(ConnectionString);
        Connection.Open();
    }
    catch (SqlException)
    {
        // Connection failed 
    }
}
Run Code Online (Sandbox Code Playgroud)

但根据这篇文章我的测试,它并没有按照我的预期方式工作。

我可以通过执行以下操作轻松解决此问题:

public void EstablishConnection()
{
    string ConnectionString = "Data Source=127.0.0.1;Initial Catalog=foo;Connection Timeout = 3; Persist Security Info =True;User ID=<id>;Password=<password>";

    try
    {
        SqlConnection Connection = new SqlConnection(ConnectionString);
        Connection.Open();
    }
    catch (SqlException)
    {
        try 
        {
          string ConnectionString = "Data Source=127.0.0.1,7777;Initial Catalog=foo;Connection Timeout = 3; Persist Security Info =True;User ID=<id>;Password=<password>";
          SqlConnection Connection = new SqlConnection(ConnectionString);
          Connection.Open();
        } 
        catch (SqlException) 
        {
           // Connection failed 
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但这感觉就像意大利面条代码和整体糟糕的做法。

此外,在实际应用程序中,我不会从配置文件中获取完整的连接字符串,而是从配置文件中提取参数并从那里构建配置字符串。

Adr*_*tti 5

首先,让我们定义一个辅助方法来创建并打开尝试所有给定端口的连接:

private SqlConnection TryEstabilishConnection(params int?[] portNumbers) {
    foreach (int? portNumber in portNumbers)
    {
        var connectionString = CreateConnectionStringBuilder();
        if (portNumber != null)
            connectionString.DataSource += $",{portNumber}";

        try {
            var connection = new SqlConnection(connectionString.ToString());
            connection.Open();

            return connection;
        }
        catch (SqlException) {
            // Attempt failed, log?
        }
    }

    // Connection failed with all given ports...
    return null;
}
Run Code Online (Sandbox Code Playgroud)

CreateConnectionStringBuilder()方法从配置中读取参数并返回一个可用的SqlConnectionStringBuilder对象:

private SqlConnectionStringBuilder CreateConnectionStringBuilder() {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

您的代码将是:

public void EstabilishConnection() {
    // You can specify more than one alternative port
    var connection = TryEstabilishConnection(null, 7777, 58900);
    if (connection == null) {
        // Oops!
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果您没有指定任何端口号,它将尝试使用默认端口号 (1433),但也会尝试与 1434 建立 UDP 连接以请求动态分配的 TCP 端口(只有在禁用此机制的情况下,替代端口的想法才有用) SQL 服务器配置)。

另请注意,有时连接因网络相关错误而无法工作,但 SQL Server 实例正在默认端口上侦听,您可能需要使用重试模式,我仅在此处概述代码:

private SqlConnection TryEstabilishConnection(params int?[] portNumbers) {
    foreach (int? portNumber in portNumbers) {
        var connectionString = CreateConnectionStringBuilder();
        if (portNumber != null)
            connectionString.DataSource += $",{portNumber}";

        var connection = TryEstabilishConnection(connectionString.ToString());
        if (connection != null)
            return connection;
    }

    // Connection failed with all given ports...
    return null;
}

private SqlConnection TryEstabilishConnection(string connectionString) {
    for (int i=0; i < RetriesOnError; ++i) {
        try {
            var connection = new SqlConnection(connectionString);
            connection.Open();

            return connection;
        }
        catch (SqlException) when (i < RetriesOnError - 1) {
            Thread.Sleep(DelayBeforeRetry);
        }
    }

    return null;
}
Run Code Online (Sandbox Code Playgroud)

这些常量只是指示性的,网络错误可能需要更长的延迟(或更多的尝试),但您最终可能需要等待太长时间才能收到连接不可用的通知(如果这种情况经常发生,那么您应该更改您的用户体验告知用户正在发生的事情):

private const int RetriesOnError = 5;
private const int DelayBeforeRetry = 1000;
Run Code Online (Sandbox Code Playgroud)

调用代码不变。您也可以使用相同的模式在正常操作期间重试,另请参阅从 C# 调用 SQL Server 时知道何时重试或失败?。在这种情况下,我建议保存有效的连接,如果默认端口不可用,则不要再次尝试。

最后一点:(Failover Partner应该)仅适用于镜像数据库,据我所知,它不是您可以在没有任何其他 SQL Server 配置的情况下使用的替代方案 Data Source(但我想说它可能是一个很好的功能)。