使用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


出現的問題:sql  Service Broker 未開啓


開啓方法:http://pan.baidu.com/share/link?shareid=1850603785&uk=3675091944


未啓用當前數據庫的 SQL Server Service Broker,因此查詢通知不受支持。如果希望使用通知,請爲此數據庫啓用 Service Broker。 
 
 
 
解決辦法: 
在Microsoft SQL Server Management Studio Express新建查詢中執行如下語句 
alter database DotShoppingCart set enable_broker 
 
若命令執行成功的話,驗證一下,執行下面SQL語句 
select IS_BROKER_ENABLED from master.sys.databases where name='DotShoppingCart'  
若出現下圖所示,則配置成功 
 
   
注:DotShoppingCart是一個數據庫     












 2 
 
擴展閱讀  
Service Broker 端點 
SQL Server 使用 Service Broker“端點”使 Service Broker 與 SQL Server 實例外部進行通信。 端點就是一個 SQL Server 對象,代表 SQL Server 進行網絡通信的功能。每個端點支持一種特定的通信類型。例如,HTTP 端點使 SQL Server 可以處理特定的 SOAP 請求。Service Broker 端點將 SQL Server 配置爲通過網絡發送和接收 Service Broker 消息。 
Service Broker 端點爲傳輸安全模式和消息轉發提供選項。Service Broker 端點偵聽特定的 TCP 端口號。 
默認情況下,SQL Server 的實例不包含 Service Broker 端點。因此,默認情況下 Service Broker 不通過網絡發送或接收消息。必須創建 Service Broker 端點才能向 SQL Server 實例外部發送消息或從該實例外部接收消息。有關創建 Service Broker 端點的詳細信息,請參閱 CREATE ENDPOINT (Transact-SQL)。一個實例可以只包含一個 Service Broker 端點。 
安全說明 
創建 Service Broker 端點後,SQL Server 將接受在該端點中指定的端口上的 TCP/IP 連接。Service Broker 傳輸安全模式要求授權後才能連接該端口。如果運行 SQL Server 的計算機啓用了防火牆,則該計算機上的防火牆配置必須允許該端點中所指定端口的傳入和傳出連接。有關 Service Broker 傳輸安全模式的詳細信息,請參閱 Service Broker 傳輸安全性。 
  
Service Broker 傳輸安全性 
Service Broker 傳輸安全性使數據庫管理員可以對與數據庫的網絡連接進行限制並且可以加密網絡上的消息。Service Broker 端點支持基於證書的身份驗證和 Windows 身份驗證。 
傳輸安全性應用於兩個實例間的網絡連接。傳輸安全性控制哪些實例可以通信並提供兩個實例間的加密。 
傳輸安全性作爲一個整體應用於實例。傳輸安全性不保護各個消息的內容,也不控制對實例中各個服務的訪問。Service Broker 對話安全性在各消息離開發送實例至到達目標實例之間,對消息進行加密。 實例使用的身份驗證類型取決於每個實例的 Service Broker 端點的 AUTHENTICATION 選項。當端點指定多個身份驗證方法時,具體使用哪個身份驗證方法取決於爲發起連接的實例指定這些方法時的












 3 
順序。協商期間,每個實例都報告其所支持的所有身份驗證類型和算法。發起方按照接受方指定的順序嘗試兩個端點都支持的身份驗證方法。這意味着,對於長時間運行的會話,消息可能通過多個連接來交換,並且連接的身份驗證可能隨着發起會話的實例不同而不同。 
Service Broker 端點支持兩種類型的加密。與身份驗證一樣,具體用於連接的加密方法取決於爲發起連接的實例指定這些方法時的順序。 
   
解析SQL Server 2005中的Service Broker 
來源:68design.net 2007年08月29日 09:08 網友評論:0條 點擊: 
740 
  SQL Server 2005中的新內容Service Broker,可用來建立以異步消息爲基礎的應用。Service Broker應用是一個或者多個程序的集合,能夠完成一套相關的任務。爲了更加深入的瞭解其涵義,讓我們來看看組成應用的各個對象。    消息器  
  消息是Service Broker應用中信息傳遞的基本單元。在Service Broker內部,消息是按發送順序進行接收,並且保證每個消息只會發送和接收一次。而且消息保證不會丟失。有時,某個消息已被髮送,但是沒有馬上收到。當發生這種情況時,Service Broker會保存消息並嘗試再次發送。消息帶有確認信息以確保經他們傳遞的信息就是他們所等待接收餓。可以傳遞的消息最大可達2G。    會話  
  當消息在Service Broker應用中傳遞使使用會話(或者對話)方法。會話一般針對特別任務生成,當任務完成以後就會被刪除。會話纔是Service Broker最主要的信息交換結構,而不是消息。會話發生在兩個服務端點之間:發起會話的服務(發起方),以及接受會話需求的服務(目的方)。    隊列  
  在Service Broker應用中,消息以隊列方式保存等待接受處理。在內部,Service Broker隊列是一種特殊的表格,能夠通過指明隊列名稱的SELECT語句進行查看,不能在隊列中執行INSERT, UPDATE, 或者DELETE語句。保存在












 4 
隊列中的消息即使重新啓動服務器也不會丟失。    服務  
  服務程序是讀取並處理隊列中的消息的程序。這種服務可以是特定的存儲程序,或者連接數據庫的不同程序。每個服務必須與隊列相關。正如先前所提到的,會話發生在服務之間。    會話組  
  會話組用來接連消息的處理過程並使之相互關聯。每個會話是會話組中的一份子。主要的概念是有些消息與其他消息相關,會話組將這些相關的會話按照要求的順序結合在一起。事實上,所進行的處理具有對會話組裏的全部消息的高級連續訪問權限,直到處理完成。  
  Service Broker 應用還有很多其他相關的部分。以上提到的各個部分在Service Broker起主要作用。您對它們越熟悉,您就會更熟練的掌握Service Broker應用的編寫。現在,我們來看如何使用Service Broker應用來實現業務交易。  
  業務處理  
  業務流程中的任務很少按照同步進行。這些流程經常由彼此獨立的任務組成,但是很可能同時發生,可能重疊,可能需要流程中別的步驟的支持。這種情況經常出現在生產產品的過程中,特別是客戶訂製的生產過程,比如汽車生產。    當有人預訂一輛定製的汽車,汽車各個部件的生產過程並不彼此依賴。例如,這些部件可以同時生產。但是在最後階段,當進行組裝時你會遇到下面的問題:    ·取決於前一步驟的步驟。  
  ·如果出現錯誤會對整個項目的成功起絕對性影響的步驟。    ·需要購買者補充信息的步驟。  
  除了這種情況以外,如果潛在購買者取消了訂單,那麼進行補償的程序也要符合邏輯。您可能對有類似特徵的業務流程比較熟悉。  
  當數據庫執行這樣的流程時,經常按一系列數據庫交易進行處理,每個交易都有單獨的基本任務。當其中一個數據庫交易被接受或退回時,這一系列相關的業務交易通常都無法以此方法完成。這些交易必須被設計成失敗時,通過邏輯判斷退回業務交易。整個業務程序都很難進行,因爲這些獨立的交易實際上是於彼此相關的,他們都包含同樣的總體目標。這是Service Broker這樣的隊列結構的實際價值所在。  












 5 
  在Service Broker應用中,目前的處理方法是可能的,也經常是人們所需要的。您能夠以這種方法建立應用模式,使模式更符合業務流程。在我們定製的汽車行業的例子中,您能夠以這樣的方式設計應用,使得跟蹤底盤的模塊和跟蹤引擎的模塊能夠同時出現。更好的,對這兩個獨立的零件的處理通過對話組可以相互聯繫。

發佈了27 篇原創文章 · 獲贊 1 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章