连接超时参数传递给SqlConnection不起作用。

5
我有以下的连接字符串:
<add name="DatabaseServerInstance"
     providerName="Microsoft.Data.SqlClient"
     connectionString="Server=xx.xx.x.xx; Database=db; User Id=user; Password=pass; TrustServerCertificate=true; Connection Timeout=2;" />

我有一个使用WPF的控件,调用MessageBox来显示数据库连接是否建立,我希望Connection.Open()能提前终止,而不是用户需要等待15秒才能显示连接失败的消息,但是Connection Timeout=2似乎没有起作用。
一个成功的连接会立即响应,而连接失败的情况,无论超时设置为多少,都会以平均时间加载。
编辑:这是sqlconnection调用的代码:
public class SqlPusher
{
    private static SqlCommand command = new SqlCommand();
    private static string connectionString = ConfigurationManager.ConnectionStrings["DatabaseServerInstance"].ConnectionString;

    public Boolean TestConnection()
    {
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            try
            {
                connection.Open();
                return true;
            }
            catch (SqlException)
            {
                return false;
            }
        }
    }

}

调试器: 调试器

1
请展示你实例化和打开SqlConnection的代码。另外,你为什么不使用异步API呢? - undefined
2
私有的静态 SqlCommand command = new SqlCommand(); <-- 你不应该将 SqlCommand 对象保存在这样的静态字段中 - SqlCommand 对象应该是短暂存在的。 - undefined
1
我认为连接超时并不包括DNS查找或SSRP(SQL Server解析协议,或通过SQL Browser服务进行实例名称查找),所以在测试时,为了排除这些可能性,请直接连接到实例的IP地址和端口(不要使用主机名和实例名称)。 - undefined
1
从我在MS代码中看到的情况来看,设置并没有真正被任何地方所遵守,所以你需要使用其他方法来处理它,例如通过在线程中包装你的代码并计时自己(从而忽略connection.Open的结果)。无论如何,在GUI代码中使用阻塞方法是不好的。另请参阅https://github.com/dotnet/efcore/issues/10991。 - undefined
1
@Aleksandar 我认为微软在这个时候不会改变这个,因为这可能会对客户造成破坏性的改变。更新文档以更清晰地说明这一点肯定是很好的。我也进行了一些测试,对于一个不可用的服务器,超时时间是30秒,对于一个可用但具有等待1分钟的SQL登录触发器的服务器,连接超时时间为2秒,它在超时之前等待了60秒。 - undefined
显示剩余18条评论
9个回答

1
你可以在这里看到ConnectionTimeout的含义:什么是sql server连接字符串中的“连接超时”? 为了实现所需的行为,你必须在SqlCommand中发送Timeout(但是这需要使用正确的ConnectionString,否则它不起作用,参见-CommandTimeout不起作用),所以无论如何你都必须在你的连接字符串中提供ConnectionTime属性,或者将其设置为0以无限等待,并通过异常退出:
   SqlCommand cmd = new SqlCommand(cmdText, connection);
   cmd.CommandTimeout = 30;

在此之后,您应该执行您的查询,即通过cmd.ExecuteNonQuery进行插入/更新/删除操作,它在内部使用超时时间。
// inside ExecuteNonQuery method
this.InternalExecuteNonQuery((TaskCompletionSource<object>) null, nameof (ExecuteNonQuery), false, this.CommandTimeout, out bool _);

1
从我在MS代码中看到的情况来看,这个设置实际上没有被任何地方遵守,所以你需要使用其他方法来处理它,例如将你的代码包装在一个线程中并计时自己(从而忽略Connection.Open的结果)。无论如何,在GUI代码中使用阻塞方法是不好的。
另请参阅https://github.com/dotnet/efcore/issues/10991以获取讨论和处理连接超时的解决方法。

这是唯一一个完全涵盖了这个话题并提出了可行解决方案的答案。我最终对页面上的代码进行了一些修改,这些修改是从GitHub上的问题评论中由@BateTech指出的确切代码段中得到的: https://github.com/dotnet/efcore/issues/10991#issuecomment-366328847 - undefined

1

我认为有几个原因。

首先,根据this,在连接字符串中使用ConnectTimeout而不是Connection Timeout。

其次,在打开连接之前,您可以设置SqlConnection.ConnectionTimeout:

using (SqlConnection connection = new SqlConnection(connectionString)) 
{
  connection.ConnectionTimeout = 2;
  
}

最后一个你可以调用SqlConnection.OpenAsync()而不是Open(),并传递一个带有超时的CancellationToken,这将在2秒后中止异步打开:
using (SqlConnection connection = new SqlConnection(connectionString))
{

  CancellationTokenSource source = new CancellationTokenSource();
  source.CancelAfter(TimeSpan.FromSeconds(2));

  try 
  {
    await connection.OpenAsync(source.Token);
  
  }
  catch (OperationCanceledException)
  {
    // Handle timeout
  }

}

0
我们在连接字符串中一直使用30作为默认的连接超时时间。13年前,有人在Stack Overflow上问了与您相同的问题。 连接字符串中的连接超时属性被忽略 似乎15是最小值,您正在尝试将值设置为低于15,这是行不通的。上面链接中的建议之一是尝试SqlConnectionStringBuilder
既然您已经按照Signalist的建议尝试过了,也许可以尝试一下我在这里粘贴的链接中Oppositional的建议。他们似乎使其工作了。

文档页面似乎没有关于最小值的任何备注: https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.connectiontimeout?view=dotnet-plat-ext-7.0我尝试将其分别设置为15和30,结果都会抛出相同的异常,这是我期望的,但是需要47秒才能完成。假设两种情况下额外的时间是建立连接所需的时间引起的,那么其中一个应该比另一个更快。我真的无法理解可能出了什么问题。 - undefined

0
验证超时错误 当遇到“超时已过期”问题时,您会收到以下一个或多个错误消息:
超时已过期。在操作完成之前,超时时间已过去或服务器未响应。
System.Data.SqlClient.SqlException (0x80131904): 连接超时已过期。在尝试消耗预登录握手确认时,超时时间已过去。这可能是因为预登录握手失败或服务器无法及时回复。连接到此服务器时所花费的时间为[预登录] 初始化=23;握手=14979; System.ComponentModel.Win32Exception (0x80004005): 等待操作超时。
System.Data.SqlClient.SqlException (0x80131904): 超时已过期。在操作完成之前,超时时间已过去或服务器未响应。System.ComponentModel.Win32Exception (0x80004005): 等待操作超时。
连接超时已过期。在尝试消耗预登录握手确认时,超时时间已过去。这可能是因为预登录握手失败或服务器无法及时回复。 连接到此服务器时所花费的时间为[预登录] 初始化=21036;握手=0;(Microsoft SQL Server,错误:-2)。
系统.InvalidOperationException:超时已过期。在从池中获取连接之前已经过了超时时间。

0

我试过了,它仍然默认为30秒超时。 - undefined

0
作为一种解决方法,您可以引入自己的超时时间限制。
int timeout = 1000;
var task = connection.OpenAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
    // task completed within timeout
} else { 
    // timeout logic
}

查看此SO问题以获取更多详细信息: 异步等待Task<T>完成并设置超时

我无法使用异步 - 这真的很令人沮丧,因为这个命令看起来很简单,在调试器中也出现了,但它仍然以默认时间通过。 - undefined
@Aleksandar,你完全可以在WPF中使用异步操作。你也不需要自己设置超时时间。整个问题都基于错误的假设,包括连接所需的时间以及涉及的步骤数量。 - undefined
@PanagiotisKanavos 连接超时是一个任意设置的值,用于演示无法连接的情况,例如在2秒后需要提示响应 - 它也可以是5秒或10秒。WPF是一个需要连接到VPN的任务,这样我理论上可以自行控制是否能够连接,因为当我连接到VPN时,响应是即时的,但在未连接时默认为30秒。为什么我不能使用自己的超时时间?毕竟,它是SqlConenctionStringBuilder的一个参数。 - undefined
我是说 Task.Delay()。通过 VPN 连接意味着你会有更多的延迟,因为操作系统会将数据包路由到远程网络并建立 TCP 连接。你应该增加超时时间,而不是减少它们。不过一旦连接建立好了,连接池意味着它会自动被重用(假设它已经通过 Dispose 释放),而不会产生相同的连接成本。 - undefined

0
我会使用“TcpClient”进行快速失败的方法,然后再进行实际的“SqlConnection”进行身份验证。
这是我的代码:
public class SqlPusher
{
    private static SqlCommand command = new SqlCommand();
    private static string connectionString = ConfigurationManager.ConnectionStrings["DatabaseServerInstance"].ConnectionString;

    public Boolean TestConnection()
    {
        SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString);

        #region quick-fail-test-using-tcp/sockets

        var server = builder.DataSource;
        var port = 1433; //default for sql server
        int TimeoutInMilliSeconds = 2000; //2 seconds

        if (server.Contains(',')) //parse "localhost,1433" into server & port
        { 
            var commaIndex = server.IndexOf(',');
            int.TryParse(server.Substring(commaIndex + 1), out port);
            server = server.Substring(0, commaIndex);
        }

        var tcpCheckPassed = false;
        try
        {
            using (var tcp = new System.Net.Sockets.TcpClient())
            {
                IAsyncResult result = tcp.BeginConnect(server, port, null, null);
                var connectResult = result.AsyncWaitHandle.WaitOne(TimeoutInMilliSeconds, true);
                if (connectResult && tcp.Connected)
                {
                    tcpCheckPassed = true;
                }
            }

        }
        catch (SocketException)
        {
            tcpCheckPassed = false;
        }

        if (!tcpCheckPassed)
            return false;  //tcp failed - no point continuing further.

        #endregion


        using (SqlConnection connection = new SqlConnection(builder.ToString()))
        {
            try
            {
                connection.Open();
                return true;
            }
            catch (SqlException)
            {
                return false;
            }
        }
    }

}

0
更改 "connection timeout = 2" 为 "connect timeout = 2"。 这对我起作用了。
<add name="DatabaseServerInstance" 
     connectionString="Server=xx.xx.x.xx; Database=db; User Id=user; Password=pass; TrustServerCertificate=true; Connect Timeout=2;" />

谢谢你的建议 - 我尝试了,但可惜没有任何改变。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接