连续调用SqlDependency.Start两次,第二次失败?

Calling SqlDependency.Start two times continuously, the second time failed?

多次调用 SqlDependency.Start 的目的是确保在执行某些其他操作(例如基于 Command 创建 SqlCacheDependency 的新实例之前它没问题。根据此处 https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldependency.start(v=vs.110).aspx(备注部分)处关于 SqlDependency.Start 的 Microsoft 文档,看起来多次调用 SqlDependency.Start 完全没问题:

Multiple calls with identical parameters (the same connection string and Windows credentials in the calling thread) are valid.

但实际上它可能会在第二次调用时失败(实际上它对我来说从来没有成功过),使得所有接下来调用 SqlDependency.Start 的尝试都失败(通过返回 false 无声地失败,没有例外抛出)。

我做的应该满足第一个限制(在上面link的备注部分提到),即所有对SqlDependency.Start的调用具有相同的参数(实际上只有1 个相同的参数,即连接字符串)。它看起来就像这样:

//at initialization step (such as in `Application_Start()` in ASP.NET MVC)
SqlDependency.Start(myConnectionString);//this usually returns OK 
//later at the time before creating an instance of SqlCacheDependency
//I tried to call the Start method again to ensure everything is ok
var ok = SqlDependency.Start(myConnectionString);//almost always false
if(ok){
    //almost never reach here ...
}

所以很难理解微软所说的(在备注部分的第一个限制中),2个调用完全相同。但是如果第二次调用失败,之后使用的任何相同调用仍然会失败(这意味着一旦我尝试多次调用它就没有任何机会成功启动它)。

当我看到登录 Sql 服务器时,我可以看到有很多消息说 找不到远程服务...因为它不存在

我不需要解决方案或变通方法,我只需要一些解释,说明为什么它不像 Microsoft 所说的那样正常工作,或者我误解了 Microsoft 所说的内容?

作为Jeroen Mostert mentioned in the comments and the docs for SqlCommand.Start()状态:

Returns

Boolean

true if the listener initialized successfully; false if a compatible listener already exists.

正如文档中的评论所述,SqlDependency.Start()SqlDependency.Stop() 将跟踪对每个调用的次数。如果对 SqlDependency.Start() 的调用次数超过对 SqlDependency.Stop() 的调用次数,它将确保后台连接是 运行 或正在建立(尽管我认为它会丢失跟踪并重置其计数您调用 SqlDependency.Stop() 的次数比调用 SqlDependency.Start() 的次数多。

Start() 错误

这可能有助于澄清 SqlDependency.Start() 可能会失败。让它失败的一种方法是从一个 AppDomain 使用不同的连接字符串多次调用它。在特定的 AppDomain 中,如果您传入不同的连接字符串,SqlDependency.Start() 抛出异常 ,除非连接字符串中的以下属性中至少有一个是与先前传递的连接字符串不同:

  1. 数据库名称
  2. 用户名

即,您应该规范化或缓存您首先传递给 SqlDependency.Start() 的连接字符串,这样您就永远不会传递给它具有不同值的字符串,例如 Max Pool Size。我认为这样做是为了避免为单个进程创建大量代理队列和连接。此外,当您稍后实际设置 SqlDependency 时,当它尝试将命令与代理队列匹配时,它可能会使用这些有区别的连接字符串属性来决定使用哪个队列。

ASP.NET生命周期

从“生命周期事件和 Global.asax 文件”下的 ASP.NET Application Life Cycle 文档中,请注意以下内容:

Application_Start 方法虽然是实例方法,但仅在应用程序启动时调用,这通常发生在应用程序的第一个 HTTP 请求期间。文档特别指出:

You should set only static data during application start. Do not set any instance data because it will be available only to the first instance of the HttpApplication class that is created.

您应该使用的方法来清理您在 Application_Start 中初始化的东西是 Application_End。当一个 web 应用程序正常停止时,您的应用程序实例 class 将被创建并在其上调用 Application_End。请注意,这可能是应用程序 class 与调用 Application_Start 的不同实例。

由于 ASP.NET 的体系结构,每个正在处理的请求都需要一个不同的 HttpApplication class 实例。这意味着将创建多个实例来处理并发请求。文档还指出,出于性能原因,应用程序 class 实例 可能 由框架缓存并用于多个请求。为了让您有机会在实例级别初始化和清理您的应用程序 class,您可以实现 InitDispose 方法。这些方法应该配置应用程序 class 的不特定于特定请求的实例变量。文档状态:

Init

Called once for every instance of the HttpApplication class after all modules have been created.

Dispose

Called before the application instance is destroyed.

但是,您提到您正在 Application_Start 中初始化全局状态(即 SqlDependency.Start())并在 Dispose() 中清理全局状态(即 SqlDependency.Stop()) .由于 Application_Start 将被调用一次并用于配置 statics/globals 并且 Dispose() 为每个应用程序调用 class 框架退休的实例(这可能发生多次在调用 Application_End() 之前的次数),您可能正在快速停止依赖。

因此,可能是在服务器用完请求后调用 SqlDependency.Stop(),在这种情况下,它会通过调用 Dispose() 来清理 HttpApplication 实例。任何通过将 SqlDependency 附加到 SqlCommand 来实际开始监视更改的尝试都应该在此之后失败。我不确定已经订阅的命令会做什么,但它们可能会在那时失败,这会触发您的代码重新订阅一个新的依赖项,然后应该会出错。这可能是您“找不到远程服务”错误的解释——您调用 SqlDependency.Stop() 太早太频繁了。