任何有实际价值的关系数据库应用程序都离不开一大堆的查询表。如果您是开发图形用户界面的专家,那么您知道这些查询表将用于加工下拉列表框中的列表。我将查询表分成两种:只读表和可改写只读表。二者的区别在于什么会导致表的改变。我认为如果需要召开员工会议或者用户会议才可以修改表的内容,那么表就是只读的。一个好的例子就是公司的产品类别表。表的内容将不会改变直到公司研发并向市场投放了新产品,或者公司进行了重组。可改写的只读表是内容相对固定的列表,但可以被最终用户修改,通常使用组合框而不用下拉列表框来展现。可改写只读表的一个例子就是称谓术语表。应用程序设计人员能够考虑到最常用的那些称谓,如Ms., Mr., Mrs.以及Dr.,但总有某个用户的头衔是您从未考虑过的而且希望把它添加进来。举个例子看看这种情况有多么常见,我最后工作的一个中型产品有一个设计良好符合第三范式的关系数据库,它包含了350—400张表,我估计了一下大约有250张表是只读的或可改写只读表。
传统的Web应用程序(三层结构应用程序的经典范例)希望尽可能地缓存这种类型的表。这样不仅可以减少往返数据库的次数,还可以降低数据库服务器的查询负载、提高某些应用场景的响应能力,例如接受了新订单。只读表很容易缓存;可以始终将表放在缓存中,偶尔需要重新加载表时,则由数据库管理员(DBA)通过某种方式重新加载缓存。我希望您的企业中很少召开对数据库基本结构和内容作修改的会议。重新刷新中间层缓存中可改写只读表则有一点点麻烦。如果缓存刷新的频率太低就无法获得您期望的效果;用户也无法立刻看到其它用户对数据的修改。支持人员可以使用另一个应用程序添加新项,然后给打算使用该项的同伴发送一条即时消息,但同伴的选择列表框中却并不包含新添加的项。更糟糕的是,如果第二个用户此时试图重新添加这条“缺失的列表项”,就会收到一条数据库错误告知该项已经存在了。由于类似问题的存在,如果可改写只读表允许多点更新,那么通常不进行缓存。
过去,程序员不得不自己手工开发解决方案,使用消息队列、进行文件输出的触发器、或者out-of-band协议来通知缓存,如果应用程序外部的某些用户更新了可改写只读表。这种“通知”解决方案只是通知缓存有行被添加或修改了,指示必须刷新缓存。如何通知缓存有哪些行改变了或者有哪些行被添加了,这一直都是个难题,是分布式数据库和分布式事务或者合并复制领域的问题。低成本的通知解决方案的做法是:只要程序收到“缓存无效“的消息就刷新整个缓存。
SqlDependency 提供了缓存解决方案
如果您正在使用SQL Server 2005和ADO.NET 2.0,那么一种新的称为查询通知的通知解决方案已内置在SqlClient数据供应商和数据库中。该问题终于有了内置且易于使用的解决方案!ASP.NET 2.0也内置了用于支持查询通知的特性。ASP.NET的Cache对象可以注册通知,甚至还可以将通知与ASP.NET的页面缓存或页面片段缓存结合在一起使用。
实现该功能的架构中包含了SQL Server 2005查询引擎、SQL Server Service Broker、sp_DispatcherProc系统存储过程,ADO.NET的SqlNotification (System.Data.Sql.SqlNotificationRequest)和SqlDependency类 (System.Data.SqlClient.SqlDependency),以及ASP.NET的Cache类。 (System.Web.Caching.Cache)简而言之,它的工作方式如下:
1.每个ADO.NET SqlCommand都包含一个Notification属性,表示对通知的请求。当执行SqlCommand时,如果存在Notification属性,那么就会在网络请求中附加一个表示通知请求的网络协议包(TDS)。
2.SQL Server使用查询通知架构注册所请求通知的订阅,然后执行命令。
3.SQL Server“监视”任何可能导致最初返回的结果集发生变化的SQL DML语句。当发生变生时,就向Service Broker服务发送消息。
4.消息可以:
a.引发通知并将通知传回注册的客户端。
b.驻留在Service Broker''s的服务队列中,高级客户端可以实现自己的处理机制。
图1. 通知服务概览
ASP.NET的SqlCacheDependency类(System.Web.Caching.SqlCacheDependency)和OutputCache指示词会使用SqlDependency实现自动的通知功能。如果ADO.NET客户端需要对查询通知拥有更多的控制权,那么可以使用SqlNotificationRequest并且手工处理Service Broker队列,实现自己喜欢的处理逻辑。深入解释Service Broker已经超出了本文的范围,A First Look at SQL Server 2005 for Developers的示例章节以及Roger Wolter的文章A First Look at SQL Server 2005 Service Broker将对您有所帮助。
在继续下面的内容之前,明确这一点是十分重要的:当结果集改变时,每个SqlNotificationRequest或者SqlDependency只收到一条通知消息。不管数据改变是由于执行INSERT语句在数据库上进行了插入、使用DELETE语句删除了一行或多行,还是使用UPDATE更新了一行或多行,通知消息都是完全一样的,通知中也不包含任何有关改变的行数或者哪些行被改变的信息。当缓存对象或者用户应用程序收到这条关于数据改变的消息时只有一种选择,刷新整个结果集或者重新注册通知。您不会接收到多条消息,当该条消息引发后,数据库中的用户订阅也就不存在了。此外查询通知框架的工作前提是:为多种不同事件发送通知总比根本不通知要好。因此不仅仅在结果集改变时会发送通知,删除或者修改了结果集引用的表、回收数据库、以及其他一些原因也会发送通知。缓存或程序对通知的响应方式通常是一样的,即刷新缓存数据并且重新注册通知。
现在我们已经了解了所有相关的概念和语义,接下来我们将从三个角度深入研究查询通知是如何工作的:
1.SQL Server的查询通知是如何实现的?可选的分发器是如何工作的?
2.SqlClient的SqlDependency和SqlNotificationRequest是如何在客户层/中间层工作的?
3.ASP.NET 2.0是如何支持SqlDependency的?
SQL Server 2005中的查询通知
在服务器一级,SQL Server分批处理客户端发送的查询。每个查询(可以将查询看作是SqlCommand.CommandText属性)只能包含一个批,尽管每个批可以包含多个T-SQL语句。SqlCommand还可以执行存储过程或者用户定义的函数,这些对象也可以包含多个T-SQL语句。在SQL Server 2005中,客户端发送的查询中还包含了其它三条信息:用于投递通知的Service Broker的service、通知标识符(一个字符串)、以及通知的超时值。如果查询请求中存在这三条信息并且查询包含了SELECT或者EXECUTE语句,那么SQL Server将“监视”该查询产生的所有结果集是否因为其他SQL Server会话而改变。如果产生了多个结果集,例如执行了某个存储过程,那么SQL Server将“监视”所有的结果集。
那么我所说的“监视”结果集是指什么?SQL Server又是如何完成该任务的呢?对结果集的变更检测是SQL Server数据库引擎的一部分,使用了SQL Server 2000中用于索引视图同步的变更检测机制。在SQL Server 2000中,Microsoft引入了索引视图的概念。 SQL Server中的视图包含了对单张表或者多张表的列的查询。 每个视图对象都有名称,可以像使用表名那样使用视图名称,例如:
CREATE VIEW WestCoastAuthors
AS
SELECT * FROM authors
WHERE state IN (''CA'', ''WA'', ''OR'')
现在您就可以使用视图了,就像在查询中使用表一样:
SELECT au_id, au_lname FROM WestCoastAuthors
WHERE au_lname LIKE ''S%''
大多数程序员都很熟悉视图,但可能并不熟悉索引视图。在一个非索引视图中,视图中的数据并不作为独立副本而存储在数据库中;每次使用视图时,视图中包含的查询将被执行。因此在上面的例子中,将执行获取WestCoastAuthors结果集的查询,然后再通过条件判定找出那些我们想要的WestCoastAuthors。索引视图则存储了数据副本,因此如果我们将WestCoastAuthors创建成一个索引视图,我们就拥有两份authors数据。您现在可以通过两种方式更新数据,或者通过索引视图,或者通过原始表。SQL Server因此需要对两份物理数据存储的变化进行检测,从而将一处的变更应用到另一处。这种变更检测机制和数据库引擎处理查询通知时使用的机制是一样的。
由于变更检测的这种实现方式,因此并非所有视图都可以创建索引。那些有关索引视图的限制条件对于查询通知也同样生效。例如:以上面的方式书写的WestCoastAuthors 视图就不能创建索引。要想在视图上创建索引,视图定义必须使用2部分命名法则,且必须显式地列出结果集中所有的列名。因此为了创建索引我们来修改一下视图的定义:
CREATE VIEW WestCoastAuthors
WITH SCHEMABINDING
AS
SELECT au_id, au_lname, au_fname, address, city, state, zip, phone
FROM dbo.authors
WHERE state in (''CA'', ''WA'', ''OR'')
只有遵循了索引视图规则的那些查询才可以使用通知。注意:尽管使用了相同的机制来判定是否一个查询结果集发生了改变,但查询通知不会导致SQL Server创建数据副本,而索引视图会。您可以在SQL Server 2005 Books Online中找到有关索引视图的一系列规则。如果提交了一个附带通知请求但是违背规则的查询,SQL Server会立刻发出一个原因为“无效查询”的通知。可是,通知发布到哪里呢?
SQL Server 2005使用Service Broker传递通知。Service Broker是SQL Server中内置的异步消息队列功能。查询通知将使用Service Broker的SERVICE。此处的SERVICE是指异步消息的目的地;可以强制要求消息必须遵循被称为合同的一组规则。每个Service Broker的SERVICE始终和某个队列,也就是物理的消息目的地相关联。查询通知合同是内置在SQL Server中的,名称为http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification.
注意:尽管合同在SQL Server中的对象名看似一个URL,但却与该位置没有任何联系。它只是一个对象名,就像dbo.authors是个表名而已。
总而言之,查询通知消息的目的地可以是任何SERVICE,只要该SERVICE支持相应的合同。使用SQL DDL定义service的语句如下所示:
CREATE QUEUE mynotificationqueue
CREATE SERVICE myservice ON QUEUE mynotificationqueue
([http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification])
GO
现在就可以使用myservice的SERVICE作为查询通知请求的目的地了。SQL Server通过将消息发送到SERVICE的方式发布通知。可以使用您自己的SERVICE或者让SQL Server选择MSDB数据库中的一个内置SERVICE。如果使用您自己的SERVICE,您必需自己编写代码读取和处理消息。但如果使用MSDB中内置的SERVICE,则使用预先写好的消息投递的代码。后面我还会再探讨这一点。
由于查询通知需要使用Service Broker,因此有一些其它要求:
1.必须在通知查询运行的数据库上开启Service Broker。在SQL Server Beta 2中,默认AdventureWorks示例数据库没有开启该功能,可以使用“ALTER DATABASE SET ENABLE_BROKER”的DDL语句开启该功能。
2.提交查询的用户必须具有订阅查询通知的权限。权限授予是在每个数据库级别上配置的;下面的DDL语句将授予用户''bob''在当前数据库中订阅的权限:
GRANT SUBSCRIBE QUERY NOTIFICATIONS TO bob
将通知发送给最终用户或者缓存
到目前为止,我们已经提交了正确的附带通知请求的查询到SQL Server。SQL Server在行集上进行监视,如果任何用户改变了行集,一条消息就被发送到我们选择的SERVICE。现在该做些什么呢?您可以自己编写代码负责在通知产生时读取消息并执行自定义的处理逻辑;或者您也可以使用内置的分发器替您进行处理。那么,让我们来看看分发器。
除非指定了自定义的SERVICE,否则查询通知将使用MSDB数据库中内置的名为 http://schemas.microsoft.com/SQL/Notifications/QueryNotificationService作为默认的SERVICE。当消息到达该SERVICE的队列时,与队列关联的sp_DispatcherProc系统存储过程自动对消息进行处理。有意思的一点是该存储过程使用了.NET编写的代码,因此必须在SQL Server 2005实例上启用“加载NET公共语言运行时(CLR)”,自动的查询通知投递才能工作。(可以在每个SQL Server实例上启用或禁用“加载.NET CLR”)
当查询通知消息到达时,sp_DispatcherProc (从现在起,我将它称为“分发器”)检查SqlDependency通知队列中的查询通知订阅列表,然后将消息发送给每个订阅者。注意:使用分发器的时候,是由服务器将数据改变的通知告知客户端的。这样做有两个好处:客户端无须对通知进行轮询,客户端无须建立和SQL Server的连接就可以接收通知。分发器使用HTTP协议或者TCP或者私有协议将通知发送给每个订阅者。可以选择是否对服务器-客户端通信进行身份验证。通知投递到订阅者后,就从活动的订阅列表中删除该订阅。记住:每个客户端的订阅只能接收一条通知;这取决于客户端是否重新提交查询以及重新订阅。
从数据库客户端使用查询通知
现在我们已经了解了所有内部细节,让我们编写一个使用查询通知的ADO.NET客户端程序。为什么在我们编写相对简单的客户端代码前要做那么多的解释呢?尽管编写代码是相当简单的,但是必须记住要遵守规则。最常见的问题就是提交了一个无效的查询以及忘记设置Service Broker和用户权限,致使这个强大功能受挫,甚至给某些beta版测试人员的印象是该功能不能工作。一点点的准备和研究就会对您大有帮助。最后,先了解内部细节也是不错的,因为后面我们将设置诸如Service Broker的SERVICEs以及分发器协议的属性,现在您已经完全了解这些术语的含义了。
您可以在ADO.NET中写一个查询通知的客户端,像我们一样使用OLE DB,甚至使用新的HTTP Web服务客户端,但需要记住的一点就是:查询通知只能通过客户端代码来实现。该特性不允许直接通过T-SQL语句或者SQLCLR存储过程来使用SqlServer数据供应商与SQL Server进行对话。
您可以使用System.Data.dll程序集中包含的两个类:SqlDependency和SqlNotificationRequest。 如果希望使用分发器自动进行通知,那么使用SqlDependency。如果希望自己处理通知消息,那么使用SqlNotificationRequest。我们来看看每个类的一个例子。
使用SqlDependency
使用SqlDependency步骤很简单。首先,创建一个包含您要接收查询通知的SQL语句的SqlCommand;将SqlCommand和SqlDependency关联起来。然后注册SqlDependency对象OnChanged的事件处理程序。接下来执行SqlCommand。您可以处理DataReader,关闭DataReader,甚至关闭相关的SqlConnection。分发器会在您的结果集发生变更时通知您。SqlDependency对象的事件是在另一个线程上引发的;这意味着您必需有心理准备处理那些事件已经引发而您的代码还在继续运行的情形,甚至在事件引发时您可能仍然在处理查询结果集。以下是代码:
using System;
using System.Data;
using System.Data.SqlClient;
static void Main(string[] args)
{
string connstring = GetConnectionStringFromConfig();
using (SqlConnection conn = new SqlConnection(connstring))
using (SqlCommand cmd =
// 2-part table names, no "SELECT * FROM ..."
new SqlCommand("SELECT au_id, au_lname FROM dbo.authors", conn))
{
try
{
// create dependency associated with cmd
SqlDependency depend = new SqlDependency(cmd);
// register handler
depend.OnChanged += new OnChangedEventHandler(MyOnChanged);
conn.Open();
SqlDataReader rdr = cmd.ExecuteReader();
// process DataReader
while (rdr.Read())
Console.WriteLine(rdr[0]);
rdr.Close();
// Wait for invalidation to come through
Console.WriteLine("Press Enter to continue");
Console.ReadLine();
}
catch (Exception e)
{ Console.WriteLine(e.Message); }
}
}
static void MyOnChanged(object caller, SqlNotificationEventArgs e)
{
Console.WriteLine("result has changed");
Console.WriteLine("Source " + e.Source);
Console.WriteLine("Type " + e.Type);
Console.WriteLine("Info " + e.Info);
}
同样的代码您也可以使用Visual Basic .NET编写,使用熟悉的WithEvents关键字和SqlDependency. 注意这段程序只接收和处理一个OnChanged事件,而不管底层行集发生了多少次改变。收到通知时我们要做的工作就是重新提交附带新的通知请求的命令,并使用该命令的执行结果刷新缓存以反映出新数据。如果将上面例子中Main() 的代码转移到一个命名的例程中,代码看起来应如下所示:
static void Main(string[] args)
{
GetAndProcessData();
UpdateCache();
// wait for user to end program
Console.WriteLine("Press Enter to continue");
Console.ReadLine();
}
static void MyOnChanged(object caller, SqlNotificationEventArgs e)
{
GetAndProcessData();
UpdateCache();
}
通过几小段文字我们就可以了解在ASP.NET 2.0中可以采用完全相同的方式,使用ASP.NET的Cache类作为数据缓存。
SqlDependency依靠内置在SQL Server 2005中的分发器建立和客户端的连接以及发送通知消息。分发器使用的是out-of-band通信而不是使用SqlConnection。这也意味着您的客户端必须可以“通过网络到达”SQL Server;防火墙或者网络地址转换可能会妨碍这种通讯。未来的beta版将允许您对端口配置有更多的控制权,从而可以更友好地通过防火墙的限制。可以通过指定SqlDependency构造方法中的参数来完全配置服务器和客户端的通信工作。示例如下:
SqlDependency depend = new SqlDependency(cmd,
null,
SqlNotificationAuthType.None,
SqlNotificationEncryptionType.None,
SqlNotificationTransports.Tcp,
10000);
SqlDependency的这个构造方法允许您选择不同于默认值的行为。所有可改变行为中最有用的就是服务器用于连接客户端所使用的传输协议。该例中使用的是SqlNotificationTransports.Tcp,服务器可以使用TCP或者HTTP。该参数的默认值是SqlNotificationTransports.Any;这样让服务器来“决定”使用哪个传输协议。如果指定Any,那么当客户端操作系统中包含了核心模式HTTP支持时,服务器将使用HTTP协议,否则使用TCP。Windows Server 2003和Windows XP with SP2都包含了核心HTTP支持。由于消息是通过网络发送的,因此您可以指定使用何种类型的身份验证。目前EncryptionType是一个参数,但在以后的beta版中该参数将被去除。目前两个值默认都是None。SqlNotificationAuthType也支持集成的身份验证。您可以显式地指定订阅超时值和SQL Server Service Broker的SERVICE名称。SERVICE名称通常设置为空,如例子中所示,但也可以显式地将内置的SqlQueryNotificationService指定为SERVICE名称。最有可能被改写的参数是SqlNotificationTransport和timeout。注意这些参数只能应用于SqlDependency,因为参数指定的是服务器端分发器的行为。使用SqlNotificationRequest的时候并不会使用分发器。
使用SqlNotificationRequest
使用SqlNotificationRequest只是在配置方面要比SqlDependency复杂一点,但主要取决于您的程序如何处理消息。当您使用SqlDependency时,服务器上的通知被发送到MSDB数据库的SqlQueryNotificationService,并由它替您处理通知。使用SqlNotificationRequest时您必须自己处理消息。这里有一个简单的示例,使用SqlNotificationRequest以及我们在本文前面定义的SERVICE。
using System;
using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;
class Class1
{
string connstring = null;
SqlConnection conn = null;
SqlDataReader rdr = null;
static void Main(string[] args)
{
connstring = GetConnectionStringFromConfig();
conn = new SqlConnection(connstring));
Class1 c = new Class1();
c.DoWork();
}
void DoWork()
{
conn.Open();
rdr = GetJobs(2);
if (rdr != null)
{
rdr.Close();
WaitForChanges();
}
conn.Dispose();
}
public SqlDataReader GetJobs(int JobId)
{
using (SqlCommand cmd = new SqlCommand(
"Select job_id, job_desc from dbo. jobs where job_id = @id",
conn))
{
try
{
cmd.Parameters.AddWithValue("@id", JobId);
SqlNotificationRequest not = new SqlNotificationRequest();
not.Id = new Guid();
// this must be a service named MyService in the pubs database
// associated with a queue called notificationqueue (see below)
// service must go by QueryNotifications contract
not.Service = "myservice";
not.Timeout = 0;
// hook up the notification request
cmd.Notification = not;
rdr = cmd.ExecuteReader();
while (rdr.Read())
Console.WriteLine(rdr[0]);
rdr.Close();
}
catch (Exception ex)
{ Console.WriteLine(ex.Message); }
return rdr;
}
}
public void WaitForChanges()
{
// wait for notification to appear on the queue
// then read it yourself
using (SqlCommand cmd = new SqlCommand(
"WAITFOR (Receive convert(xml,message_body) from notificationqueue)",
conn))
{
object o = cmd.ExecuteScalar();
// process the notification message however you like
Console.WriteLine(o);
}
}
使用SqlNotificationRequest的强大之处(当然也包括额外的工作)就是由您自己等待和处理查询通知。如果您使用SqlDependency,那么在接收到查询通知之前您甚至无需连接数据库。事实上不需要为了接收SqlNotificationRequest的通知而进行等待;可以每隔一段时间轮询一次消息队列。SqlNotificationRequest另一个用处就是编写一个特别的应用程序,即使通知已经激活了,该应用程序也不会运行。当应用程序启动后,它将连接到消息队列并决定缓存中(由于上次运行应用程序)哪些内容是无效的。
探讨一下这样的应用程序,它们为了等待一条通知需要花费好几个小时或者几天,这种应用程序向我们提出了问题: “如果数据不发生改变,那么通知何时消失呢?” 唯一可以导致通知消失的情况(例如,清理了数据库的订阅表)就是通知被引发或者通知过期。数据库管理员如果为那些始终留在数据库中的订阅(因为它们会使用SQL资源并且增加查询和数据更新的成本)而恼火的话,可以在SQL Server中手动地清理通知。首先查询SQL Server 2005动态视图,找出那些不想要的通知订阅,然后使用下面的命令删除它们:
-- look at all subscriptions
SELECT * FROM sys.dm_qn_subscriptions
-- pick the ID of the subscription that you want, then
-- say its ID = 42
KILL QUERY NOTIFICATION SUBSCRIPTION 42
在ASP.NET中使用SqlCacheDependency
通知功能还被连接到ASP.NET的Cache类中。ASP.NET 2.0中CacheDependency类可以派生子类,其中SqlCacheDependency封装了SqlDependency,其行为与ASP.NET中其它的CacheDependency一样。 SqlCacheDependency超越了SqlDependency,因为它可以工作在SQL Server 2005或者SQL Server早期版本上。当然对于SQL Server 2005之前的版本,它的实现是完全不同的。
如果您使用的是SQL Server早期版本,那么SqlCacheDependency通过建立在被“监视”表上的触发器进行工作。这些触发器将变更信息记录到另一张SQL Server表中,然后轮询那张表。可以对启用相依性功能的表以及轮询的时间间隔进行配置。SQL Server 2005以前版本的实现细节已经超出了本文讨论的范围,更多信息请参阅Improved Caching in ASP.NET 2.0。
使用SQL Server 2005时,SqlCacheDependency封装了一个与前面ADO.NET示例中类似的SqlDependency实例。以下是使用SqlCacheDependency的一个简短示例:
// called from Page.Load
CreateSqlCacheDependency(SqlCommand cmd)
{
SqlCacheDependency dep = new SqlCacheDepedency(cmd);
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60);
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetValidUntilExpires(true);
Response.AddCacheDependency(dep);
}
一个很棒且易于使用的特性就是SqlCacheDependency可以和页面缓存或者页面片段缓存配合使用。您可以通过一个特殊的ASP.NET指示词OutputCache声明式地启用所有SqlCommands的查询通知功能。该方式为页面中所有SqlCommand设置相同的SqlDependency。对于SQL Server 2005数据库其声明方式如下所示:
<%OutputCache SqlDependency="CommandNotification" ... %>
注意CommandNotification是关键字,其含义为“使用SQL Server 2005和SqlDependency”,如果使用早期的SQL Server版本,那么该指示参数的语法是完全不同的。同样,只有在特别的操作系统版本上运行ASP.NET 2.0时,才会启用关键字CommandNotification设置的值。
热心的通知
SQL Server查询通知的设计策略是:频繁通知用户总比忘记通知用户要好。尽管绝大多数情况您收到通知都是由于他人修改了表中的某一行致使缓存无效,但并非总是如此。例如,如果DBA回收了数据库您会收到通知。如果查询中引用的任何表被修改、删除、或者清空,您也会收到通知。由于查询通知回占用SQL Server资源,如果SQL Server受到资源不足的压力,就可能从内部表中删除查询通知,这种情况下客户端也会收到通知。由于每个通知请求都包含了一个超时值,如果订阅超时也会通知您。
如果使用SqlDependency,那么分发器会将所有信息封装到一个SqlNotificatio
原文出处:http://www.cnblogs.com/zhwenstudy/archive/2008/10/06/1305074.html