zoukankan      html  css  js  c++  java
  • 使用SqlDependency监听SqlServer2005数据库变更通知

            背景需求:对于数据中表A数据字段的变更,需要引发相应业务逻辑,插入或更新相关表或字段。在以往的方式我们多会在数据库端下文章,建立相应触发器,来完 成业务逻辑操作。不过这种方式仅适用于单纯对于数据操作的需求,可是当我们要完成更复杂的业务需求是却不太容易了(虽然sql05已经支持托管代码的使用 了)。可能你会想到我们可以轮询数据库相关表或视图,来发现数据的变化,可是这对于性能和即时性却是个不容易取舍的问题。不过在 SqlServer2005中有了新的方案,那就是查询通知。

            查询通知是在 Microsoft SQL Server 2005 中以及 ADO.NET 2.0 的 System.Data.SqlClient 命名空间中引入的。查询通知建立在 Service Broker 基础结构的基础上,使应用程序可以在数据更改时收到通知。如果应用程序提供数据库中信息的缓存(例如 Web 应用程序),需要在源数据更改时接收通知,此功能特别有用。

    通过三种方式可以使用 ADO.NET 实现查询通知:

    1. 低级实现由 SqlNotificationRequest 类提供,该类公开服务器端功能,使您可以对通知请求执行命令。

    2. 高级实现由 SqlDependency 类提供,该类提供源应用程序与 SQL Server 之间通知功能的高级抽象,使您可以使用相关性来检测服务器中的更改。大多数情况下,这是托管客户端应用程序通过适用于 SQL Server 的 .NET Framework 数据提供程序利用 SQL Server 通知功能的最简单、最有效的方法。

    3. 此外,使用 ASP.NET 2.0(或更高版本)构建的 Web 应用程序可以使用 SqlCacheDependency 帮助器类。

            如果应用程序需要通过刷新显示或缓存来响应基础数据中的更改,查询通知非常有用。如果执行相同命令生成的结果集与最初检索到的结果集不同,则 Microsoft SQL Server 可允许 .NET Framework 应用程序向 SQL Server 发送命令和请求通知。服务器上生成的通知通过队列发送,供以后处理。

    您可以为 SELECT 和 EXECUTE 语句设置通知。使用 EXECUTE 语句时,SQL Server 会为执行的命令而不是 EXECUTE 语句本身注册通知。该命令必须满足 SELECT 语句的要求和限制。当注册通知的命令包含多个语句时,数据库引擎会为批处理中的每个语句创建一个通知。

    使用查询通知的应用程序有一组通用的要求。必须正确配置数据源才能支持 SQL 查询通知,并且用户必须具有正确的客户端和服务器端权限。

    要使用查询通知,必须符合下列条件:

    1.使用 SQL Server 2005 或 SQL Server 2008。

    2.对数据库启用查询通知。

    3.确保用于连接数据库的用户 ID 具有必要的权限。

    4.使用 SqlCommand 对象执行有效的 SELECT 语句,包含关联的通知对象 — SqlDependency 或 SqlNotificationRequest。

    5.提供所监视的数据更改时用于处理通知的代码。

    下面就以一个例子来说明使用SqlDependency的整个流程

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Data.SqlClient;
    using System.Data;
    using System.Configuration;
    using System.Windows.Forms;

    namespace CaptureWeb
    {
        public class SQLServiceBroker
        {

            private string connectionStr = ConfigurationManager.ConnectionStrings["ConnectionString"].ToString();

            private string sqlStr = "";

            private SqlConnection connection = null;

            public delegate void UIDelegate();

            private UIDelegate uidel = null;

            public Form form = null;

            /// <summary>
            ///
            /// </summary>
            /// <param name="TableName"></param>
            /// <param name="ColumnNames"></param>
            public SQLServiceBroker(string TableName, List<string> ColumnNames)
            {
                string columns = "";
                foreach (string str in ColumnNames)
                {
                    if (columns != "")
                        columns = columns + ",";
                    columns = columns + "[" + str + "]";
                }
                this.sqlStr = string.Format("select {0} From [dbo].[{1}]", columns, TableName);
            }

            /// <summary>
            ///
            /// </summary>
            /// <param name="constr"></param>
            /// <param name="TableName"></param>
            /// <param name="ColumnNames"></param>
            public SQLServiceBroker(string constr, string TableName, List<string> ColumnNames)
                : this(TableName, ColumnNames)
            {
                this.connectionStr = ConfigurationManager.ConnectionStrings[constr].ToString();
            }


            /// <summary>
            ///
            /// </summary>
            ~SQLServiceBroker()
            {
                StopDependency();
                connection.Dispose();
            }

            /// <summary>
            ///
            /// </summary>
            /// <returns></returns>
            public bool EnoughPermission()
            {

                SqlClientPermission perm = new SqlClientPermission(System.Security.Permissions.PermissionState.Unrestricted);
                try
                {
                    perm.Demand();
                    return true;
                }
                catch (System.Exception)
                {
                    return false;
                }
            }

            /// <summary>
            ///
            /// </summary>
            /// <param name="uidelegate"></param>
            public void InitDependency(UIDelegate uidelegate)
            {

                SqlDependency.Stop(connectionStr);
                SqlDependency.Start(connectionStr);
                if (connection == null)
                    connection = new SqlConnection(connectionStr);

                if (!EnoughPermission())
                    throw new Exception("没有权限(SqlClientPermission)!");
                if (uidelegate == null)
                    throw new Exception("回调方法未指定(UIDelegate)!");
                if (connection == null)
                    throw new Exception("未初始化(InitDependency)!");
                this.uidel = uidelegate;

            }

            /// <summary>
            /// 传入窗体对象,以防止委托有需要访问UI层控件是引发的“从不是创建控件的线程访问它”
            /// </summary>
            /// <param name="form1"></param>
            /// <param name="uidelegate"></param>
            public void InitDependency(Form form1, UIDelegate uidelegate)
            {
                InitDependency(uidelegate);
                this.form = form1;
            }

            /// <summary>
            ///
            /// </summary>
            public void StartDependency()
            {
                //这里很奇怪,每次都需要新的command对象
                using (SqlCommand command = new SqlCommand(sqlStr, connection))
                {
                    command.Notification = null;
                    SqlDependency dependency = new SqlDependency(command);
                    dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
                    if (connection.State != ConnectionState.Open)
                        connection.Open();
                    command.ExecuteNonQuery();
                    command.Dispose();
                }
            }

            /// <summary>
            ///
            /// </summary>
            public void StopDependency()
            {
                SqlDependency.Stop(connectionStr);
                if (connection != null)
                    connection.Close();
            }

            /// <summary>
            ///
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
            {
                //注销监测事件
                SqlDependency dependency = (SqlDependency)sender;
                dependency.OnChange -= dependency_OnChange;
                //放在移除事件之后又很大必要,防止ui层调用更新相同表时,进入循环出发调用
                //uidel.Invoke();
                //uidel();
                //使用from.Invoke调用防止访问界面控件引发“从不是创建控件的线程访问它”
                if (form != null)
                    form.Invoke(uidel);
                else
                    uidel();
                //再次启动监听
                StartDependency();
            }

        }
    }


    调用方式:

    SQLServiceBroker broker;
    private void button1_Click(object sender, EventArgs e)
    {
        //需要监测的列
        List<string> columns = new List<string>();
        columns.Add("test1");
        columns.Add("test2");
        string table = "test";
        broker = new SQLServiceBroker(table, columns);
        //实例化毁掉函数
        SQLServiceBroker.UIDelegate uidel = new SQLServiceBroker.UIDelegate(writeCon);
        //初始化,及传入回调函数
        broker.InitDependency(uidel);
        //初始化,传入窗体对象对于需要委托中访问ui控件的情况
        //broker.InitDependency(this, uidel);
        //启动监听
        broker.StartDependency();
        MessageBox.Show("启动");
    }

    private void writeCon()
    {
        MessageBox.Show("changed");
    }


    代码比较简单,都有说明,这里有必要注意几点问题:

    1.首先需要SQL Server 2005上执行ALTER DATABASE <DatabaseName> SET ENABLE_BROKER;语句让相应的数据库启用监听服务,以便支持SqlDependency特性。


    2.对于SqlCommand的cmdText有特殊要求,其中不能用*,不能用top,不能用函数,包括聚合函数,不能用子查询,包括where后的子查询,不能用外连接,自连接,不能用临时表,不能用变量,不能用视图,不能垮库,表名之前必须加类似dbo数据库所有者这样的前缀。


    3.其中在使用当中发现SqlConnection对象应该是一直存在的,因此在此示例中升级为属性,如果将它生命在StartDependency方法体中,出现只能调用一次的情况,因为对于SqlDependency需要connection对象的存在。


    4.在使用委托传入调用方法是,如果方法有访问界面UI控件的情况,需要传入窗体对象,以form.Invoke(uidel);的方式调用,否则会引发“从不是创建控件的线程访问它”异常。


    5.对于回调函数中需要更新正在监听的表时防止循环调用造成死循环,请在调用委托之前先移除onchange事件dependency.OnChange -= dependency_OnChange;


    整个项目Demo:http://cid-9601b7b7f2063d42.skydrive.live.com/self.aspx/Code/SQLServiceBroker.rar


    相关文章:http://support.microsoft.com/kb/555893/


    CodeProject:http://www.codeproject.com/KB/database/chatter.aspx


    MSDN: http://msdn.microsoft.com/zh-cn/library/t9x04ed2.aspx

    作者:FreezeSoul
    出处:http://hi.baidu.com/freezesoul
    欢迎转载,但请保留此段声明,且在文章页面明显位置给出原文连接。

    From: http://hi.baidu.com/freezesoul/blog/item/148778cbbd892216bf09e643.html

  • 相关阅读:
    My first blog in cnblog
    浅析JavaScript中this储存
    input 文本框密码框的只读属性
    Js 数组——filter()、map()、some()、every()、forEach()、lastIndexOf()、indexOf()
    jquery使用$.getJson()跨域大数据量请求方法
    JS中关于clientWidth offsetWidth scrollWidth 等的含义及区别
    JS性能优化
    npm 创建 node.js 项目
    css 垂直居中的几种方法
    字符串转数组
  • 原文地址:https://www.cnblogs.com/freezesoul/p/1553220.html
Copyright © 2011-2022 走看看