【導讀】本文中,我想通過一個簡單的Windows桌面表單示例(基於SQL Server 2005的范例數據庫AdventureWorks)向讀者展示SQL Server 2005中這種新的主動地通知工作機理。由於Visual Studio 2005的革命性變化,你可以極為容易地把這個例子更改到Web應用程序場合下。
一、引言
在開發多人同時訪問的Web應用程序(其實不只這類程序)時,開發人員往往會在緩存策略的設計上狠下功夫。這是因為,如果將這種環境下不常變更的數據臨時存放在應用程序服務器或是用戶機器上的話,可以避免頻繁地往返訪問數據庫—而數據庫訪問是要符出昂貴代價的。以往在低版本的SQL Server(SQL Server 2000及以前版本)中,當需要提供數據庫內他人更新後的狀況時,主要是通過輪詢數據庫機制來提供對數據庫的不斷查詢;也可能是借助於存儲於數據庫表格中的觸發器或者通過消息隊列方式來達到通知目的。如今,作為微軟.Net 2.0戰略的重要組成部分之一的SQL Server 2005首次引入了主動式通知(Query Notification)機制。SQL Server 2005在所使用數據更改時,會主動地通知你。這種新的設計模式會讓你在系統數據未更新時,減輕浪費網絡來回輪詢的負擔,從而有可能極大地提高系統性能。
本文中,我想通過一個簡單的Windows桌面表單示例(基於SQL Server 2005的范例數據庫AdventureWorks)向讀者展示SQL Server 2005中這種新的主動地通知工作機理。
【另注】由於Visual Studio 2005的革命性變化,你可以極為容易地把這個例子更改到Web應用程序場合下。
二、SQL Server 2005中的主動式通知
主動式通知(也稱為“查詢通知”),是微軟ADO.Net和SQL Server小組協作開發的新成果。它允許你對數據進行緩沖並且僅在SQL Server中的數據發生變化時才發出通知;一旦接到通知,你就可以刷新相應的緩沖區或者采取其它必要的措施。
在SQL Server 2005中引入的一種新特征“Service Broker”使得查詢通知成為可能。Service Broker把隊列機制引入到數據庫管理中,它使用一組隊列與服務進行通訊,而服務反過來也知道如何往回通訊以調用相應的實體。其實,這些隊列和服務都是一些與表、視圖和存儲過程一樣的類對象。盡管完全可以在SQL Server內使用Service Broker,但是ADO.Net也知道如何與Service Broker進行通訊以觸發這種機制並且從Service Broker中檢索回通知。
【作者注】Service Broker是SQL Server 2005中新增加的一項重要服務,旨在為日趨流行的面向服務的架構(Service-OrIEnted Architecture,即“SOA”)在數據庫存儲級提供基礎性支持。
其實,完整的通知架構還是比較復雜的。其中參與的組件可能包括:SQL Server 2005查詢引擎、Service Broker、系統存儲過程sp_DispatcherProc;ADO.NET的SqlNotification類(System.Data.Sql.SqlNotificationRequest)、SqlDependency類(System.Data.Sql.SqlDependency);以及ASP.Net 2.0中新的Cache類(System.Web.Caching.Cache)等等。
下圖1展示了SQL Server 2005中的主動通知機制及其與客戶端ASP.Net頁面交互的示意圖。
圖1:SQL Server 2005主動通知機制示意圖
上面的運行邏輯大致如下:
(1)SqlCommand類中提供了一個Notification屬性,用於存儲通知相關的設置。當SqlCommand執行時,會讓傳遞該執行需求的TDS協議附加上通知的相應信息。
(2)SQL Server 2005收到該需求後,為這個需求注冊通知,並執行該需求自身的SQL語句;
(3)接下來,SQL Server 2005會監控後續執行的DML語法,並確定是否能夠影響前一步返回給前端的數據集;一旦有影響,則會立即發送一個消息到Service Broker;
(4)Service Broker的隊列中有消息後,可能發生如下情況:
a)Notification在前端應用程序偵聽的隊列中放入消息,由ADO.Net的下層自動讀取消息並觸發事件;
b)在Service Broker內的消息持續保留著,較高級的前端應用程序會自己處理這個消息。
如前所述,由於SQL Server 2005的通知機制在基層上依賴於Services Broker,所以要發出通知的數據庫必須讓Services Broker啟動。Services Broker利用SQL Server 2005所提供的隊列創建異步通知。而通知其實就是一組Services Broker內置好的服務(也即是標准的消息、發送的消息及發送消息的規則等等)。下圖2中,我們通過SQL Server Management Studio中的對象資源管理器窗口查看每一個范例數據庫AdventureWorks的“Services Broker”節點下屬相關的設置情況:
圖2:SQL Server 2005在Services Broker中已經准備好主動式通知設置情況
前面已經提到,我們想通過SQL Server 2005的范例數據庫AdventureWorks進行試驗;所以,若要讓程序能夠收到通知,必須先啟動該數據庫的相應服務,同時還要允許登錄的帳戶訂閱這種查詢通知。下面SQL語句實現創建相應的設置:
--啟動Service Broker服務支持
ALTER DATABASE AdventureWorks SET ENABLE_BROKER
--
--【提示】我們無法直接在sp_dboption中(使用“EXEC sp_dboption AdventureWorks”語句)
--看出某個數據庫是否啟動了Service Broker服務
--需要觀察sys.databases的is_broker_enabled字段才知道是否已經啟動—使用如下語句:
SELECT * FROM sys.databases
--允許某個賬號訂閱查詢
GRANT SUBSCRIBE QUERY NOTIFICATIONS TO [YourComputerNameUserName]
三、示例分析
有了上面的分析和相應的SQL設置後,現在讓我們來觀察一個使用SQL Server 2005主動式通知機制的Windows桌面應用程序的示例。程序相應表單的設計界面如下圖3所示:
圖3:表單的設計界面
Public Class DeskNotification
Dim conn As New SqlConnection(ADONET20.My.Settings.AdventureWorksConnection)
Delegate Sub PopulateList()
Private Sub DeskNotification_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
SqlDependency.Start(ADONET20.My.Settings.AdventureWorksConnection)
‘取得初始數據
ListProducts()
End Sub
Private Sub productListBox_SelectedIndExchanged(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles productListBox.SelectedIndExchanged
Dim strItem As String = productListBox.SelectedItem.ToString
lblId.Text = strItem.Substring(0, strItem.IndexOf("-") - 1)
txtPrice.Text = strItem.Substring(strItem.IndexOf(":") + 1)
End Sub
Private Sub btnUpdate_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles btnUpdate.Click
Dim cnn As New SqlConnection(ADONET20.My.Settings.AdventureWorksConnection)
If lblId.Text = "無" Then
MessageBox.Show("請選擇某一條記錄")
Exit Sub
End If
cnn.Open()
Dim cmd As New SqlCommand( _
"UPDATE Production.Product SET ListPrice=" & txtPrice.Text & " WHERE
ProductID=" & lblId.Text, _
cnn)
cmd.ExecuteNonQuery()
cnn.Close()
End Sub
Sub OnDependencyChanged(ByVal sender As Object, ByVal e As SqlNotificationEventArgs)
'SqlDependency對象的OnChanged事件觸發時
'要執行的業務邏輯
Dim dR As DialogResult
dR = MessageBox.Show("數據已經完畢,要更新數據嗎?", e.Info.ToString, _
MessageBoxButtons.YesNo, MessageBoxIcon.Question)
If dR = Windows.Forms.DialogResult.Yes Then
'由表單的主線程實現數據更新
Try
Me.Invoke(New PopulateList(AddressOf ListProducts))
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End If
End Sub
Public Sub ListProducts()
'重新裝載數據
' SqlDependency設置後,僅會注冊一次的事件通知
Dim dep As New SqlDependency()
'設置SqlDependency對象的OnChanged事件發生時要調用哪個事件處理器
AddHandler dep.OnChange, AddressOf OnDependencyChanged
'限制查詢的范圍,避免太大的范圍的大量用戶都影響到這個范圍內的數據以致使SQL Server
'頻繁地觸發通知
Using cmd As New SqlCommand( _
"SELECT ProductID, Name, ListPrice FROM Production.Product " & _
"WHERE ProductID BETWEEN @Start AND @End", conn)
With cmd
.Parameters.Add(New SqlParameter("@Start", Data.SqlDbType.Int))
.Parameters.Add(New SqlParameter("@End", Data.SqlDbType.Int))
.Parameters(0).Value = txtStart.Text
.Parameters(1).Value = txtEnd.Text
End With
'自動幫助我們設置SqlCommand的Notification屬性所需的SqlNotificationRequest對象
'可以通過Debug來觀察SqlCommand對象執行前後的關系
dep.AddCommandDependency(cmd)
productListBox.Items.Clear()
conn.Open()
Dim reader As SqlDataReader = cmd.ExecuteReader()
While reader.Read()
productListBox.Items.Add(reader("ProductID") & " - " & _
reader("Name").ToString & ": " & reader("ListPrice").ToString)
End While
End Using
conn.Close()
End Sub
Private Sub DeskNotification_FormClosing(ByVal sender As System.Object,
ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
SqlDependency.Stop(ADONET20.My.Settings.AdventureWorksConnection)
End Sub
End Class
在這個例子中,我們首先在Global.asax文件內的Application_Start事件加入通過SqlDependency類的靜態方法Start啟動監聽。注意,這個Start方法需要傳遞數據庫連接字符串。它將完成如下相應操作:
◆打開一條新的不經過數據庫連接池的到SQL Server 2005的連接;
◆在服務器上創建一個新的隊列,並賦予唯一名稱;
◆在該隊列上創建一個唯一名稱的服務;
◆在服務器上創建一個新的存儲過程,在客戶端不再監聽隊列時,清除掉上述的臨時創建的各種對象;
◆偵聽隊列所收到的更改通知。
【注意】在上面的例子中,盡管你可以通過SqlCommand實例中的SQL更改記錄,但並沒有自動更新列表框內的記錄數據,而是在收到SQL Server記錄改變的通知後,通過事件觸發指到OnDenpedencyChanged函數調用主線程重新執行ListProducts方法,從相關數據表讀取更新後的記錄來重設列表框的內容。
四、何時使用主動式通知機制
查詢通知是針對於並不經常改變的數據而設計的。最好把它應用於服務器端的應用程序(例如ASP.Net或remoting)而不是客戶端應用程序(例如Windows表單應用程序)。記住,每一個通知請求都要在SQL Server中注冊。如果你擁有大量的都有通知請求的客戶端應用程序,那麼這可能會導致你的服務器產生資源問題。鑒於此,微軟推薦,對於客戶端應用程序,你應該限制使用查詢通知的最大並行用戶數不多於十個。
對於大規模應用程序來說,查詢通知可能是一種強有力的幫助,而不用簡單地添加越來越多的服務器以滿足要求。設想,有一家大型的為成千上百萬用戶提供在線軟件更新服務的軟件公司。不是使每一個用戶的更新操作都觸發服務器上的另一個查詢來確定需要哪些組件,而是能夠緩沖查詢結果並且可以直接從該緩存中服務匹配的查詢。
對於較小規模的情況而言,下拉式列表框是另一種典型的數據集;此時該數據集更新的次數一般不如請求的次數多。產品列表、州列表、國家列表、供應商、銷售人,甚至更多不太需要頻繁改變的信息正是使用上述通知機制的較好候選。
五、小結
盡管查詢通知是.NET 2.0中最重要的特征之一,但是目前它仍然難與其它優秀特征(例如ASP.NET中的泛型或UI魔術等)相銜接。然而,無論你使用它來防止針對於含有數百個項的下拉列表框的連續的反復查詢,還是使用它來管理基於Web的上百萬的客戶端計算機的更新,它都能有效地幫助你減少資源開支。就其最簡單的應用來看,借助於ASP.Net OutputCache指令(或通過在你的Web應用程序的中間層或Web服務中構建一種復雜緩沖的機制),查詢通知可以成為創建可擴展的具有響應性的應用程序的強有力的協作開發工具。
【另注】本文基於SQL Server 2005 Express Edition調試通過。另外,盡管查詢通知可以與SQL Server Express(SSE)一起使用,但是SSE數據庫必須是一個命名的實例(命名的實例是安裝選項之一)。