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

时间:2017-08-21 13:39:29

标签: c# sql-server service-broker sqldependency sqlcachedependency

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

  

具有相同参数的多个调用(调用线程中的相同连接字符串和Windows凭据)有效。

但实际上它可能会失败(对我来说真的没有成功)第二次调用,使得所有下一次尝试调用SqlDependency.Start失败(通过返回false静默,不抛出任何异常)

我所做的应该满足第一个限制(在上面链接的备注部分中提到),即对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 Server中看到日志时,我可以看到有很多消息说无法找到远程服务...因为它不存在

我不需要解决方案或解决这个问题,我只需要解释一下为什么它不能像微软所说的那样工作,或者我误解了微软所说的内容?

1 个答案:

答案 0 :(得分:1)

如评论中的Jeroen MostertSqlCommand.Start()的文档所述:

  

返回

     

Boolean

     

true,如果侦听器成功初始化; false,如果已经存在兼容的侦听器。

正如文档中的说明所描述的,SqlDependency.Start()SqlDependency.Stop()将跟踪每个呼叫的数量。如果对SqlDependency.Start()的呼叫数量超过对SqlDependency.Stop()的呼叫数量,它将确保后台连接正在运行或正在建立(尽管我认为如果您呼叫{{ 1}}的次数比您致电SqlDependency.Stop()的次数要多。

SqlDependency.Start()错误

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

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

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

ASP.NET生命周期

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

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

  

您应该在应用程序启动期间仅设置静态数据。不要设置任何实例数据,因为它仅对创建的HttpApplication类的第一个实例可用。

用于清理在Application_Start中初始化的内容的方法是Application_Start。当Webapp正常停止时,将创建您的应用程序类的实例,并在其上调用Application_End。请注意,与调用Application_End相比,这可能是应用程序类的实例。

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

  

Init

     

在创建所有模块之后,为Dispose类的每个实例调用一次。

     

Dispose

     

在应用程序实例销毁之前调用。

但是,您提到您正在HttpApplication中初始化全局状态(即SqlDependency.Start())并在Application_Start中清除全局状态(即SqlDependency.Stop())。由于以下事实:Dispose()将被调用一次并用于配置静态变量/全局变量,而框架退役的每个应用程序类实例均被调用Application_Start(这可能在{{1}之前发生多次) }),那么您可能很快就停止了依赖。

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