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

Setting up a C# method that tries to reconnect to a SQL Server on a different port after first failure

我正在尝试设置一种方法,该方法将尝试首先在默认端口 (1433) 上连接到 SQL 服务器,然后在失败时在其他端口(如 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 
    }
}

但基于这个 article 和我的测试,它没有按我预期的方式工作。

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

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 
        }
    }
}

但这感觉就像意大利面条代码和整体不良做法。

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

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

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;
}

CreateConnectionStringBuilder() 方法从配置中读取参数和 return 一个随时可用的 SqlConnectionStringBuilder 对象:

private SqlConnectionStringBuilder CreateConnectionStringBuilder() {
    // ...
}

您的代码将是:

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

请注意,如果您未指定任何端口号,它将尝试使用默认端口号 (1433),但也会尝试通过 UDP 连接到 1434 以 请求 动态分配的TCP端口(然后替代端口想法只有在SQL服务器配置中禁用此机制时才有用)。

另请注意,有时由于网络相关错误,连接无法正常工作,但 SQL 服务器实例正在侦听默认端口,您可能需要使用 retry 模式,我只是在这里概述代码:

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;
}

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

private const int RetriesOnError = 5;
private const int DelayBeforeRetry = 1000;

调用代码不变。您也可以使用相同的 模式 在正常操作期间重试,另请参阅 Know when to retry or fail when calling SQL Server from C#?。在这种情况下,我建议 保存 有效的连接 如果它不可用,请不要使用默认端口重试。

最后一点:Failover Partner(应该)仅适用于镜像数据库,AFAIK 它不是 替代方案 Data Source 您可以在没有任何其他 SQL 服务器配置(但我想说这可能是一个不错的功能)。