Verwendung von SqlNotificationRequest und Erkennen von Benachrichtigungen
Im folgenden Szenario wird gezeigt, wie die SqlNotificationRequest in einer Windows Forms-Anwendung verwendet wird. Zur Veranschaulichung der Funktionsweise von Benachrichtigungen wird in dieser Beispielanwendung eine Service Broker-Warteschlage mithilfe eines Arbeitsthreads auf Benachrichtigungsmeldungen hin überwacht.
Informationen zum Beispiel
Bei der Beispielanwendung handelt es sich um eine Windows Forms-Anwendung, die eine Datenmenge aus der SQL Server 2005-Beispieldatenbank AdventureWorks in ein DataSet lädt. Das DataSet wird mithilfe eines SqlDataAdapter-Objekts gefüllt und anschließend an ein DataGridView-Steuerelement gebunden. Damit Benachrichtigungen empfangen werden können, wird ein SqlNotificationRequest-Objekt erstellt und an ein vom SqlDataAdapter verwendetes SqlCommand-Objekt gebunden. Zum Abrufen der Benachrichtigungen erstellt die Anwendung einen Arbeitsthread, der eine Verbindung mit der vordefinierten Service Broker-Warteschlange herstellt und diese auf Meldungen überprüft. Wenn eine Benachrichtigung eingeht, wird die Meldung aus der Warteschlange abgerufen, und der Benutzeroberflächenthread wird benachrichtigt.
Einrichten der Datenbank
Damit das Beispiel ordnungsgemäß ausgeführt wird, müssen die folgenden Transact-SQL-Anweisungen auf dem Datenbankserver ausgeführt werden.
USE AdventureWorks ;
GO
CREATE QUEUE ContactChangeMessages ;
CREATE SERVICE ContactChangeNotifications
ON QUEUE ContactChangeMessages
([http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification]) ;
CREATE ROUTE
ContactChangeRoute
WITH SERVICE_NAME = 'ContactChangeNotifications',
ADDRESS = 'LOCAL' ;
GO
Weitere Informationen zum Einrichten von Service Broker-Warteschlangen finden Sie in der Onlinedokumentation zu SQL Server 2005 unter "Service Broker Programming Basics", "CREATE QUEUE (Transact-SQL)" und "CREATE SERVICE (Transact-SQL)".
Erstellen der Beispielanwendung
Bei der Anwendung handelt es sich um eine Windows Forms-Anwendung mit einem einzelnen Formular.
So erstellen Sie die Beispielanwendung
-
Erstellen Sie ein neues Windows-Anwendungsprojekt mit dem Namen Data Listener.
-
Wenn Sie Visual Basic verwenden, fügen Sie Verweise auf System.Data.dll und System.Xml.dll hinzu.
-
Wählen Sie im Forms-Designer das Standardformular aus. Ändern Sie im Eigenschaftenraster die Text-Eigenschaft in Contacts.
-
Fügen Sie dem Formular ein Label-Steuerelement hinzu. Docken Sie das Label-Steuerelement am unteren Rand des Formulars an.
-
Fügen Sie dem Formular ein Button-Steuerelement hinzu. Ändern Sie dessen Text-Eigenschaft in Get Contacts.
-
Fügen Sie dem Formular ein Checkbox-Steuerelement hinzu. Ändern Sie dessen Text-Eigenschaft in Re-register.
-
Fügen Sie dem Formular eine DataGridView hinzu.
-
Öffnen Sie das Klassenmodul des Formulars, und fügen Sie am Anfang der Datei oberhalb der Klassendefinition folgenden Code hinzu:
[Visual Basic]
Option Strict On
Imports System
Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlClient
Imports System.Threading
[C#]
using System.Data.Sql;
using System.Data.SqlClient;
using System.Threading;
-
Fügen Sie im Deklarationsabschnitt der Klasse die folgenden Elemente hinzu:
[Visual Basic]
Private changeCount As Integer = 0
Private Const tableName As String = "Contacts"
Private Const statusMessage As String = "{0} changes have occurred."
' The following objects are reused
' for the lifetime of the application.
Private dataToWatch As DataSet = Nothing
Private connection As SqlConnection = Nothing
Private command As SqlCommand = Nothing
' The Service Name is required to correctly
' register for notification.
' The Service Name must be already defined with
' the Service Broker for the database you are querying.
Private Const ServiceName As String = "ContactChangeNotifications"
' Specify how long the notification request
' should wait before timing out.
' This value waits for 10 minutes.
Private NotificationTimeout As Integer = 600
[C#]
private int changeCount = 0;
private const string tableName = "Contacts";
private const string statusMessage = "{0} changes have occurred.";
// The following objects are reused
// for the lifetime of the application.
private DataSet dataToWatch = null;
private SqlConnection connection = null;
private SqlCommand command = null;
// The Service Name is required to correctly
// register for notification.
// The Service Name must be already defined with
// Service Broker for the database you are querying.
private const string ServiceName = "ContactChangeNotifications";
// Spercify how long the notification request
// should wait before timing out.
// This value waits for 10 minutes.
private int NotificationTimeout = 600;
-
Fügen Sie drei Hilfsmethoden, GetConnectionString, und GetSQL, and GetListenerSQL, hinzu. Die definierte Verbindungszeichenfolge verwendet die integrierte Sicherheit. Sie müssen sicherstellen, dass das verwendete Konto über die erforderlichen Datenbankberechtigungen verfügt und dass für die Beispieldatenbank AdventureWorks Benachrichtigungen aktiviert sind. Weitere Informationen finden Sie unter Besonderheiten bei der Verwendung von Abfragebenachrichtigungen. Außerdem muss für die Datenbank eine benutzerdefinierte Warteschlange definiert werden. Weitere Informationen finden Sie oben unter "Datenbank-Setup".
[Visual Basic]
Private Function GetConnectionString() As String
' To avoid storing the connection string in your code,
' you can retrive it from a configuration file.
' In general client applications don't need to incur
' overhead of connection pooling.
Return "Data Source=localhost;Integrated Security=SSPI;" & _
"Initial Catalog=AdventureWorks;Pooling=False;"
End Function
Private Function GetSQL() As String
Return "SELECT ContactID, FirstName, LastName, " & _
"EmailAddress, EmailPromotion " & _
"FROM Person.Contact " & _
"WHERE EmailPromotion IS NOT NULL;"
End Function
Private Function GetListenerSQL() As String
' Note that ContactChangeMessages is the name
' of the Service Broker queue that must
' be already defined.
Return "WAITFOR ( RECEIVE * FROM ContactChangeMessages);"
End Function
[C#]
private string GetConnectionString()
{
// To avoid storing the connection string in your code,
// you can retrive it from a configuration file.
// In general, client applications don't need to incur the
// overhead of connection pooling.
return "Data Source=localhost;Integrated Security=SSPI;" +
"Initial Catalog=AdventureWorks;Pooling=False;";
}
private string GetSQL()
{
return "SELECT ContactID, FirstName, LastName, " +
"EmailAddress, EmailPromotion " +
"FROM Person.Contact " +
"WHERE EmailPromotion IS NOT NULL;";
}
private string GetListenerSQL()
{
// Note that ContactChangeMessages is the name
// of the Service Broker queue that must
// be already defined.
return "WAITFOR ( RECEIVE * FROM ContactChangeMessages);";
}
-
Damit die Anwendung bei Änderungen an Daten auf dem Server benachrichtigt wird, muss ein sekundärer Thread gestartet werden, der die Service Broker-Warteschlange auf neue Meldungen überwacht. Sobald eine Meldung eingeht, muss der Arbeitsthread die Meldung empfangen und an den Benutzeroberflächenthread weiterleiten. Fügen Sie dem Modul des Formulars folgenden Code hinzu:
[Visual Basic]
Private Sub StartListener()
' A seperate listener thread is needed to
' monitor the queue for notifications.
Dim listener As New Thread(AddressOf Listen)
listener.Name = "Query Notification Watcher"
listener.Start()
End Sub
Private Sub Listen()
Using connection As SqlConnection = _
New SqlConnection(GetConnectionString())
Using command As New SqlCommand(GetListenerSQL(), connection)
connection.Open()
' Make sure we don't time out before the
' notification request times out.
command.CommandTimeout = NotificationTimeout + 15
Dim reader As SqlDataReader = command.ExecuteReader()
Do While reader.Read()
' Empty queue of messages.
' Application logic could parse
' the queue data and
' change its notification logic.
Loop
Dim args() As Object = {Me, EventArgs.Empty}
Dim notify As New EventHandler( _
AddressOf OnNotificationComplete)
' Notify the UI thread that a notification
' has occurred.
Me.BeginInvoke(notify, args)
End Using
End Using
End Sub
Private Sub OnNotificationComplete( _
ByVal sender As Object, ByVal e As EventArgs)
changeCount += 1
Me.Label1.Text = String.Format(statusMessage, changeCount)
' The user can decide to register
' and request a new notification by
' checking the check box on the form.
GetData(CheckBox1.Checked)
End Sub
[C#]
private void StartListener()
{
// A seperate listener thread is needed to
// monitor the queue for notifications.
Thread listener = new Thread(Listen);
listener.Name = "Query Notification Watcher";
listener.Start();
}
private void Listen()
{
using (SqlConnection connection =
new SqlConnection(GetConnectionString()))
{
using (SqlCommand command =
new SqlCommand(GetListenerSQL(), connection))
{
connection.Open();
// Make sure we don't time out before the
// notification request times out.
command.CommandTimeout = NotificationTimeout + 15;
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
// Empty queue of messages.
// Application logic could parse
// the queue data and
// change its notification logic.
}
object[] args = { this, EventArgs.Empty };
EventHandler notify =
new EventHandler(OnNotificationComplete);
// Notify the UI thread that a notification
// has occurred.
this.BeginInvoke(notify, args);
}
}
}
private void OnNotificationComplete(object sender, EventArgs e)
{
++changeCount;
label1.Text = String.Format(statusMessage, changeCount);
// The user can decide to register
// and request a new notification by
// checking the CheckBox on the form.
GetData(checkBox1.Checked);
}
-
Fügen Sie im nächsten Schritt den folgenden Code im Click-Ereignishandler für die Schaltfläche des Formulars ein. Dieser Code initialisiert das SqlConnection-Objekt und das SqlCommand-Objekt, die zum Ausführen eines Befehls zum Registrieren einer Benachrichtigung erforderlich sind.
[Visual Basic]
changeCount = 0
Me.Label1.Text = String.Format(statusMessage, changeCount)
If connection Is Nothing Then
connection = New SqlConnection(GetConnectionString())
End If
If command Is Nothing Then
' GetSQL is a local procedure SQL string.
' You might want to use a stored procedure
' in your application.
command = New SqlCommand(GetSQL(), connection)
End If
If dataToWatch Is Nothing Then
dataToWatch = New DataSet()
End If
GetData(CheckBox1.Checked)
[C#]
changeCount = 0;
label1.Text = String.Format(statusMessage, changeCount);
if (connection == null)
{
connection = new SqlConnection(GetConnectionString());
}
if (command == null)
{
// GetSQL is a local procedure SQL string.
// You might want to use a stored procedure
// in your application.
command = new SqlCommand(GetSQL(), connection);
}
if (dataToWatch == null)
{
dataToWatch = new DataSet();
}
GetData(checkBox1.Checked);
-
Bei Anforderung durch den Benutzer erstellt die nachfolgende GetData-Methode das SqlNotificationRequest-Objekt, füllt das Raster mit Daten und startet einen Hintergrundüberwachungsthread.
[Visual Basic]
Private Sub GetData(ByVal Register As Boolean)
' Empty the dataset so that there is only
' one batch worth of data displayed.
dataToWatch.Clear()
' Make sure the command object does not already have
' a notification object associated with it.
command.Notification = Nothing
If Register Then
' Create and bind the SqlNotificationRequest object
' to the command object.
Dim snr As New SqlNotificationRequest()
snr.UserData = New Guid().ToString()
snr.Options = ServiceName
' If the a timeout occurs, a notifcation
' will occur indicating that is the
' reason for the notification.
snr.Timeout = NotificationTimeout
command.Notification = snr
End If
Using adapter As New SqlDataAdapter(command)
adapter.Fill(dataToWatch, tableName)
Me.DataGridView1.DataSource = dataToWatch
Me.DataGridView1.DataMember = tableName
End Using
If Register Then
' Start the background listener.
StartListener()
End If
End Sub
[C#]
private void GetData(bool Register)
{
// Empty the dataset so that there is only
// one batch of data displayed.
dataToWatch.Clear();
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
if (Register)
{
// Create and bind the SqlNotificationRequest object
// to the command object.
SqlNotificationRequest snr =
new SqlNotificationRequest();
snr.UserData = new Guid().ToString();
snr.Options = ServiceName;
// If a time-out occurs, a notification
// will indicate that is the
// reason for the notification.
snr.Timeout = NotificationTimeout;
command.Notification = snr;
}
using (SqlDataAdapter adapter =
new SqlDataAdapter(command))
{
adapter.Fill(dataToWatch, tableName);
dataGridView1.DataSource = dataToWatch;
dataGridView1.DataMember = tableName;
}
if (Register)
{
// Start the background listener.
StartListener();
}
}
-
Erstellen Sie abschließend einen FormClosed-Ereignishandler und fügen den folgenden Code hinzu:
[Visual Basic]
If connection IsNot Nothing Then
connection.Close()
End If
[C#]
if (connection != null)
{
connection.Close();
}
Wenn auf die Get Data-Schaltfläche geklickt wird, lädt die Anwendung das DataGridView-Steuerelement mit den Daten aus der AdventureWorks-Datenbank. Wenn das Re-register-Kontrollkästchen aktiviert wird, registriert die Anwendung weiterhin Änderungsbenachrichtigungen nach jeder Benachrichtigung. Wenn das Re-register-Kontrollkästchen nicht aktiviert wird, hebt die Anwendung nach einer Benachrichtigung die Registrierung für sich selbst auf und nimmt keine weiteren Benachrichtigungen entgegen, bis das Re-register-Kontrollkästchen aktiviert wird und die DataGridView durch Klicken auf die Get Data-Schaltfläche aktualisiert wird.
Führen Sie die Anwendung aus, aktivieren Sie das Re-register-Kontrollkästchen, und klicken Sie auf die Get Data-Schaltfläche. Das Datenraster-Steuerelement sollte mit Daten aus der AdventureWorks-Datenbank gefüllt werden.
Aktualisieren Sie zum Testen der Anwendung eine der Spalten, die von der Anwendung mithilfe von Transact-SQL-Befehlen wie dem folgenden abgerufen wird:
UPDATE Person.Contact SET LastName = 'Smith' WHERE ContactID = 1
Führen Sie zum Zurücksetzen der Daten folgenden Befehl aus:
UPDATE Person.Contact SET LastName = 'Achong' WHERE ContactID = 1
Bei jeder Datenänderung wird die entsprechende Änderung im Formular der Anwendung im DataGridView-Steuerelement und im Label-Steuerelement für die Statusanzeige an unteren Rand des Formulars angezeigt, wobei die Statusanzeige die Anzahl der Änderungen an der Datenbank seit dem letzten Laden der DataGridView angibt.
Deaktivieren Sie das Re-register-Kontrollkästchen, aktualisieren Sie die Datenbank erneut, und beobachten Sie, wie diese Änderung im Formular angezeigt wird. Durch nachfolgende Änderungen an der Datenbank werden jedoch keine weiteren Benachrichtigungen ausgelöst, und die Änderungsanzeigen im Formular der Anwendung bleiben daher unverändert.