Click to Rate and Give Feedback
MSDN
MSDN Library
Visual Studio 2005
Visual Studio
Accessing Data
ADO.NET
 Using SqlDependency in a Windows Ap...
This page is specific to
Microsoft Visual Studio 2005/.NET Framework 2.0

Other versions are also available for the following:
 
Using SqlDependency in a Windows Application 

The following scenario shows how to use the SqlDependency class in a Windows Forms application. To demonstrate how notifications work, you will create two applications: a watcher to process query notifications, and an updater to modify the data that the watcher is monitoring.

NoteNote

The sample code assumes that you have enabled query notifications by executing the scripts in Enabling Query Notifications.

Creating the Watcher Application

The watcher application is a Windows Forms application that loads product data into a DataSet from the AdventureWorks SQL Server 2005 sample database. The DataSet is filled using a SqlDataAdapter object that is bound to a DataGridView control. In order to receive notifications, a SqlDependency object is created and bound to the SqlCommand object used by the SqlDataAdapter. The SqlDependency object exposes a single event, OnChange. The handler that is registered to process the notifications performs the necessary actions to switch from the notifying thread pool thread to the UI thread and then re-registers to receive notifications.

To create the watcher application

  1. Create a new Windows Application project named "Data Watcher".

  2. In Forms Designer, select the default form. Change the Text property to Inventory Watcher.

  3. Add a Label control to the form. Dock the label control to the bottom of the form.

  4. Add a ListBox control in the upper left of the form. Size it for approximately three lines of text.

  5. Add a DataGridView below the ListBox control.

  6. Add a Button control to the form and position it to the right of the ListBox control. Change its Text property to Get Data.

  7. Open the form's class module and add the following code to the top of the file, above the class definition.

    Visual Basic
    Option Strict On
    Option Explicit On
    
    Imports System.ComponentModel
    Imports System.Data.SqlClient
    Imports System.Security.Permissions
    
    using System.Data.SqlClient;
    using System.Security.Permissions;
  8. In the declaration section of the class, add the following items:

    Visual Basic
    Private changeCount As Integer = 0
    
    Private Const tableName As String = "Inventory"
    Private Const statusMessage As String = _
      "{0} changes have occurred."
    
    ' The following objects are reused
    ' for the lifetime of the application.
    Private connection As SqlConnection = Nothing
    Private command As SqlCommand = Nothing
    Private dataToWatch As DataSet = Nothing
    C#
    private int changeCount = 0;
    
    private const string tableName = "Inventory";
    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;
  9. In the form, create a new method called CanRequestNotifications. This method will verify the application has permissions to request notifications from the server.

    Visual Basic
    Private Function CanRequestNotifications() As Boolean
        ' In order to use the callback feature of the
        ' SqlDependency, the application must have
        ' the SqlClientPermission permission.
        Try
            Dim perm As New SqlClientPermission( _
                PermissionState.Unrestricted)
    
            perm.Demand()
            Return True
    
        Catch ex As Exception
            Return False
        End Try
    End Function
    C#
    private bool CanRequestNotifications()
    {
        // In order to use the callback feature of the
        // SqlDependency, the application must have
        // the SqlClientPermission permission.
        try
        {
            SqlClientPermission perm =
                new SqlClientPermission(
                PermissionState.Unrestricted);
    
            perm.Demand();
    
            return true;
        }
        catch
        {
            return false;
        }
    }
  10. In the form's Load event, use the return value from CanRequestNotifications to set the Enabled property of the form's only button.

    Visual Basic
    Private Sub Form1_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load
    
        Button1.Enabled = CanRequestNotifications()
    End Sub
    C#
    private void Form1_Load(object sender, EventArgs e)
    {
        Button1.Enabled = CanRequestNotifications();
    }
  11. Add two helper methods, GetConnectionString and GetSQL. The defined connection string uses integrated security. You will need to verify that the account you are using has the necessary database permissions and that the sample database, AdventureWorks, has notifications enabled. For more information, see Enabling Query Notifications.

    Visual Basic
    Private Function GetConnectionString() As String
        ' To avoid storing the connection string in your code,
        ' you can retrive it from a configuration file.
        Return "Data Source=(local);Integrated Security=true;" & _
         "Initial Catalog=AdventureWorks;"
    End Function
    
    Private Function GetSQL() As String
        Return "SELECT Production.Product.ProductID, " & _
        "Production.Product.Name, Production.Location.Name AS Location, " & _
        "Production.ProductInventory.Quantity FROM Production.Product " & _
        "INNER JOIN Production.ProductInventory " & _
        "ON Production.Product.ProductID = " & _
        "Production.ProductInventory.ProductID " & _
        "INNER JOIN Production.Location " & _
        "ON Production.ProductInventory.LocationID = " & _
        "Production.Location.LocationID " & _
        "WHERE ( Production.ProductInventory.Quantity <= @Quantity ) " & _
        "ORDER BY Production.ProductInventory.Quantity, Production.Product.Name;"
    End Function
    C#
    private string GetConnectionString()
    {
        // To avoid storing the connection string in your code,
        // you can retrive it from a configuration file.
        return "Data Source=(local);Integrated Security=true;" +
          "Initial Catalog=AdventureWorks;Pooling=False;";
    }
    
    private string GetSQL()
    {
        return "SELECT Production.Product.ProductID, " +
        "Production.Product.Name, Production.Location.Name AS Location, " +
        "Production.ProductInventory.Quantity FROM " +
        "Production.Product INNER JOIN Production.ProductInventory " +
        "ON Production.Product.ProductID = " +
        "Production.ProductInventory.ProductID " +
        "INNER JOIN Production.Location " +
        "ON Production.ProductInventory.LocationID = " +
        "Production.Location.LocationID " +
        "WHERE ( Production.ProductInventory.Quantity <= @Quantity ) " +
        "ORDER BY Production.ProductInventory.Quantity, " +
        "Production.Product.Name;";
    }
  12. In order to receive notifications when data on the server changes, the application needs an event handler that matches the signature of the OnChangeEventHandler delegate. The procedure needs to catch the event and switch from the worker thread to the UI thread. Add the following code to the form's module:

    Visual Basic
    Private Sub dependency_OnChange( _
        ByVal sender As Object, ByVal e As SqlNotificationEventArgs)
    
        ' This event will occur on a thread pool thread.
        ' It is illegal to update the UI from a worker thread
        ' The following code checks to see if it is safe
        ' update the UI.
        Dim i As ISynchronizeInvoke = CType(Me, ISynchronizeInvoke)
    
        ' If InvokeRequired returns True, the code
        ' is executing on a worker thread.
        If i.InvokeRequired Then
            ' Create a delegate to perform the thread switch
            Dim tempDelegate As New OnChangeEventHandler( _
                AddressOf dependency_OnChange)
    
            Dim args() As Object = {sender, e}
    
            ' Marshal the data from the worker thread
            ' to the UI thread.
            i.BeginInvoke(tempDelegate, args)
    
            Return
        End If
    
        ' Remove the handler since it's only good
        ' for a single notification
        Dim dependency As SqlDependency = _
            CType(sender, SqlDependency)
    
        RemoveHandler dependency.OnChange, _
           AddressOf dependency_OnChange
    
        ' At this point, the code is executing on the
        ' UI thread, so it is safe to update the UI.
        changeCount += 1
        Me.Label1.Text = String.Format(statusMessage, changeCount)
    
        ' Add information from the event arguments to the list box
        ' for debugging purposes only.
        With Me.ListBox1.Items
            .Clear()
            .Add("Info:   " & e.Info.ToString())
            .Add("Source: " & e.Source.ToString())
            .Add("Type:   " & e.Type.ToString())
        End With
    
        ' Reload the dataset that's bound to the grid.
        GetData()
    End Sub
    C#
    private void dependency_OnChange(
       object sender, SqlNotificationEventArgs e)
    {
        // This event will occur on a thread pool thread.
        // Updating the UI from a worker thread is not permitted.
        // The following code checks to see if it is safe to
        // update the UI.
        ISynchronizeInvoke i = (ISynchronizeInvoke)this;
    
        // If InvokeRequired returns True, the code
        // is executing on a worker thread.
        if (i.InvokeRequired)
        {
            // Create a delegate to perform the thread switch.
            OnChangeEventHandler tempDelegate =
                new OnChangeEventHandler(dependency_OnChange);
    
            object[] args = { sender, e };
    
            // Marshal the data from the worker thread
            // to the UI thread.
            i.BeginInvoke(tempDelegate, args);
    
            return;
        }
    
        // Remove the handler, since it is only good
        // for a single notification.
        SqlDependency dependency =
            (SqlDependency)sender;
    
        dependency.OnChange -= dependency_OnChange;
    
        // At this point, the code is executing on the
        // UI thread, so it is safe to update the UI.
        ++changeCount;
        label1.Text = String.Format(statusMessage, changeCount);
    
        // Add information from the event arguments to the list box
        // for debugging purposes only.
        listBox1.Items.Clear();
        listBox1.Items.Add("Info:   " + e.Info.ToString());
        listBox1.Items.Add("Source: " + e.Source.ToString());
        listBox1.Items.Add("Type:   " + e.Type.ToString());
    
        // Reload the dataset that is bound to the grid.
        GetData();
    }
  13. In order to receive notifications, the application must register a SqlDependency object with the SqlCommand object used to obtain the application's data. Add a method called GetData as follows:

    Visual Basic
    Private Sub GetData()
        ' 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
    
        ' Create and bind the SqlDependency object
        ' to the command object.
        Dim dependency As New SqlDependency(command)
        AddHandler dependency.OnChange, AddressOf dependency_OnChange
    
        Using adapter As New SqlDataAdapter(command)
            adapter.Fill(dataToWatch, tableName)
    
            Me.DataGridView1.DataSource = dataToWatch
            Me.DataGridView1.DataMember = tableName
        End Using
    End Sub
    C#
    private void GetData()
    {
        // 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;
    
        // Create and bind the SqlDependency object
        // to the command object.
        SqlDependency dependency =
            new SqlDependency(command);
        dependency.OnChange += new
            OnChangeEventHandler(dependency_OnChange);
    
        using (SqlDataAdapter adapter =
            new SqlDataAdapter(command))
        {
            adapter.Fill(dataToWatch, tableName);
    
            dataGridView1.DataSource = dataToWatch;
            dataGridView1.DataMember = tableName;
        }
    }
  14. Add an event handler for the Click event in the form's only button, and put the following code in the handler body:

    Visual Basic
    Private Sub Button1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
        changeCount = 0
        Me.Label1.Text = String.Format(statusMessage, changeCount)
    
        ' Remove any existing dependency connection, then create a new one.
        SqlDependency.Stop(GetConnectionString())
        SqlDependency.Start(GetConnectionString())
    
        If connection Is Nothing Then
            connection = New SqlConnection(GetConnectionString())
        End If
        If command Is Nothing Then
            ' GetSQL is a local procedure that returns
            ' a paramaterized SQL string. You might want
            ' to use a stored procedure in your application.
            command = New SqlCommand(GetSQL(), connection)
    
            Dim prm As New SqlParameter("@Quantity", SqlDbType.Int)
            prm.Direction = ParameterDirection.Input
            prm.DbType = DbType.Int32
            prm.Value = 100
            command.Parameters.Add(prm)
        End If
    
        If dataToWatch Is Nothing Then
            dataToWatch = New DataSet()
        End If
    
        GetData()
    End Sub
    C#
    private void button1_Click(object sender, EventArgs e)
    {
        changeCount = 0;
        label1.Text = String.Format(statusMessage, changeCount);
    
        // Remove any existing dependency connection, then create a new one.
        SqlDependency.Stop(GetConnectionString());
        SqlDependency.Start(GetConnectionString());
    
        if (connection == null)
        {
            connection = new SqlConnection(GetConnectionString());
        }
    
        if (command == null)
        {
            // GetSQL is a local procedure that returns
            // a paramaterized SQL string. You might want
            // to use a stored procedure in your application.
            command = new SqlCommand(GetSQL(), connection);
    
            SqlParameter prm =
                new SqlParameter("@Quantity", SqlDbType.Int);
            prm.Direction = ParameterDirection.Input;
            prm.DbType = DbType.Int32;
            prm.Value = 100;
            command.Parameters.Add(prm);
        }
        if (dataToWatch == null)
        {
            dataToWatch = new DataSet();
        }
    
        GetData();
    }
  15. In the form's FormClosed event, add the following code to clean up the dependency and database connections:

    Visual Basic
    Private Sub Form1_FormClosed(ByVal sender As System.Object, _
        ByVal e As System.Windows.Forms.FormClosedEventArgs) _
        Handles MyBase.FormClosed
        SqlDependency.Stop(GetConnectionString())
        If connection IsNot Nothing Then
            connection.Close()
        End If
    End Sub
    C#
    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        SqlDependency.Stop(GetConnectionString());
        if (connection != null)
        {
            connection.Close();
        }
    }

With the watcher application completed, you will need to create the updater application and then run them together.

Creating the Updater Application

The updater application provides a simple interface to modify the set of data that is being monitored by the watcher application. The updater in this example is also a Windows Application, but any type of application can be used to update the data. Follow these steps to create the updater application:

  1. Create a new Windows Application project called "Data Updater".

  2. In Forms Designer, select the default form. In the property grid, change the Text property to "Inventory Updater".

  3. Add a Label control and a TextBox control to form. Change the Text property of the label to Product ID and the Name property of the text box to "txtProductID".

  4. Add another label and text box. Change the Text property of the label to Quantity and the Name property of the text box to "txtQuantity".

  5. Add a Button control and set its Text property to "Update".

  6. Add the following directives to the form class:

    Visual Basic
    Option Strict On
    Option Explicit On
    
    Imports System.Data
    Imports System.Data.SqlClient
    
    using System.Data
    using System.Data.SqlClient;
  7. In the declaration section of the class, add the following items:

    Visual Basic
    ' The following objects are reused
    ' for the lifetime of the application.
    Private connection As SqlConnection = Nothing
    Private command As SqlCommand = Nothing
    C#
    // The following objects are reused
    // for the lifetime of the application.
    private SqlConnection connection = null;
    private SqlCommand command = null;
  8. Add two helper methods, GetConnectionString and GetSQL, to the form. They should look like the following:

    Visual Basic
    Private Function GetConnectionString() As String
        ' To avoid storing the connection string in your code,
        ' you can retrive it from a configuration file.
        Return "Data Source=(local);Integrated Security=true;" & _
          "Initial Catalog=AdventureWorks;"
    End Function
    
    Private Function GetSQL() As String
        Return "UPDATE Production.ProductInventory " & _
        "SET Production.ProductInventory.Quantity = @Quantity " & _
        "WHERE Production.ProductInventory.ProductID = @ProductID;"
    End Function
    C#
    private string GetConnectionString()
    {
        // To avoid storing the connection string in your code,
        // you can retrive it from a configuration file.
        return "Data Source=(local);Integrated Security=true;" +
          "Initial Catalog=AdventureWorks;";
    }
    
    private string GetSQL()
    {
        return "UPDATE Production.ProductInventory " +
        "SET Production.ProductInventory.Quantity = @Quantity " +
        "WHERE Production.ProductInventory.ProductID = @ProductID;";
    }
  9. In the Click event of the Get Data button, add the following code:

    Visual Basic
    Private Sub Button1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
    
        Dim productID As Integer = CInt(txtProductID.Text)
        Dim quantity As Integer = CInt(txtQuantity.Text)
    
        If connection Is Nothing Then
            connection = New SqlConnection(GetConnectionString())
            connection.Open()
        End If
    
        If command Is Nothing Then
            ' GetSQL is a local procedure that returns
            ' a paramaterized SQL string. You might want
            ' to use a stored procedure in your applicaiton.
            command = New SqlCommand(GetSQL(), connection)
    
            Dim param As New SqlParameter("@Quantity", SqlDbType.Int)
            param.Direction = ParameterDirection.Input
            param.DbType = DbType.Int32
            command.Parameters.Add(param)
    
            param = New SqlParameter("@ProductID", SqlDbType.Int)
            param.Direction = ParameterDirection.Input
            param.DbType = DbType.Int32
            command.Parameters.Add(param)
        End If
    
        command.Parameters("@Quantity").Value = quantity
        command.Parameters("@ProductID").Value = productID
    
        Dim rowsAffected As Integer = command.ExecuteNonQuery()
    
        MessageBox.Show(rowsAffected & " records updated.", "Update")
    End Sub
    C#
    private void button1_Click(object sender, EventArgs e)
    {
        int productID = Convert.ToInt32(txtProductID.Text);
        int quantity = Convert.ToInt32(txtQuantity.Text);
    
        if (connection == null)
        {
            connection = new SqlConnection(GetConnectionString());
            connection.Open();
        }
    
        if (command == null)
        {
            // GetSQL is a local procedure that returns
            // a paramaterized SQL string. You might want
            // to use a stored procedure in your application.
            command = new SqlCommand(GetSQL(), connection);
    
            SqlParameter param = new
                SqlParameter("@Quantity", SqlDbType.Int);
            param.Direction = ParameterDirection.Input;
            param.DbType = DbType.Int32;
            command.Parameters.Add(param);
    
            param = new SqlParameter("@ProductID", SqlDbType.Int);
            param.Direction = ParameterDirection.Input;
            param.DbType = DbType.Int32;
            command.Parameters.Add(param);
        }
    
        command.Parameters["@Quantity"].Value = quantity;
        command.Parameters["@ProductID"].Value = productID;
    
        int rowsAffected = command.ExecuteNonQuery();
    
        MessageBox.Show(rowsAffected + " records updated.", "Update");
    }
  10. Finally, add the following code to the form's FormClosed event handler:

    Visual Basic
    Private Sub Form1_FormClosed(ByVal sender As System.Object, _
        ByVal e As System.Windows.Forms.FormClosedEventArgs) _
        Handles MyBase.FormClosed
    
        If connection IsNot Nothing Then
            connection.Close()
        End If
    End Sub
    C#
    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        if (connection != null)
        {
            connection.Close();
        }
    }

Testing the Application

Compile and run both applications. Click Get Data on the watcher application to fill the application's grid with data and register a notification request. Examine the data in the grid to find a product ID with an inventory quantity of zero. Switch to the updater application, enter the product ID and a new quantity of one, and click Update. You should see the watcher application automatically update itself moments after the updater changes the data on the server. Observe that the ListBox control displays information about the update.

See Also

Tags What's this?: Add a tag
Community Content   What is Community Content?
Add new content RSS  Annotations
Run away events, fixed      Mark Lakata   |   Edit   |   Show History
At first, I had a run away sequence of events constantly firing, even though the data base was not changing.
The hint was Info=Invalid in the SqlNotificationEventArgs.Info field. I think this means that the original "SELECT" statement was bogus. I think the problem was that my table reference was "mytable" instead of "dbo.mytable". You need a two-part table reference, according to this:

http://msdn.microsoft.com/en-US/library/ms181122.aspx

that seemed to have fixed my problem.
Tags What's this?: Add a tag
Flag as ContentBug
Is it normal for changeCount to skyrocket?      ITWS2   |   Edit   |   Show History
My changeCount increments by several thousand in just a few seconds and continues at the same pace while the program is running. Is this normal? Is this a byproduct of my DB? The changeCount seems to advance even when there's no appreciable change in the data returned by the query I use. How do I get this code to only report a change when the data actually changes?
Tags What's this?: Add a tag
Flag as ContentBug
Finally Got SqlDependency to work on vista home 32 bit ;vs 2008; sql server 2008      RussTee1   |   Edit   |   Show History
Hi:

We are using Vista Home (32 bit PC)
Visual Studio 2008 and SQL SERVER 2008
The original example for
Using SqlDependency in a Windows Application as written above did not work for me.

I can update the database and display the grid results fine.
But there was no notification.

However playing with
the following lines:
' Remove any existing dependency connection, then create a new one.
SqlDependency.Stop(GetConnectionString(), My.Settings.queue)
SqlDependency.Start(GetConnectionString(), My.Settings.queue)
Dim dependency As New SqlDependency(command, "service=ContactChangeNotifications;Local database=AdventureWorks", 432000)
where My.Settings.queue = ContactChangeMessages

And trying the steps below I finally got it to work.

references:
http://msdn.microsoft.com/en-us/library/a52dhwx7%28VS.80%29.aspx
http://msdn.microsoft.com/en-us/library/ms172133(VS.80).aspx
ALTER DATABASE AdventureWorks SET ENABLE_BROKER;
CREATE QUEUE ContactChangeMessages;
CREATE SERVICE ContactChangeNotifications
ON QUEUE ContactChangeMessages
([http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification]);
Stored procedures cannot have NOCOUNT ON and each query needs its own QUEUE and SERVICE      RussTee1   |   Edit   |   Show History
When using stored procedures
Missing from above was the very important
Don't use NOCOUNT ON in the stored procedure.

http://social.msdn.microsoft.com/forums/en-US/adodotnetdataproviders/thread/31fcb8b4-e18b-415c-bdd5-48a3bfa1bac2


I did notice that there were more info = error being generated when using a stored procedure in the command rather than using the text in the command, when doing multiple "sql dependencies" from the same client application.



I also discovered that I needed a separate:
CREATE QUEUE ... ;
CREATE SERVICE ...;
for each sql query I want a notification of a data change for, for multiple clients
and
one client with multiple sql queries.

permissions to get SqlDependency in a Windows Application to work      RussTee1   |   Edit   |   Show History

using:
SqlDependency.Stop(GetConnectionString(), My.Settings.queue)
SqlDependency.Start(GetConnectionString(), My.Settings.queue)


After reviewing a bunch of posts about permissions and sqldependency I discovered that the following worked for me.
1) for all cases I needed:
ALTER AUTHORIZATION ON DATABASE::AdventureWorks TO sa;

2) I then started playing with windows vs sql server authentication'
a. for windows authentication - step 1 was enough
b. if the user had a sql sign in then a needed to add
GRANT SEND on service:: ContactChangeNotifications to [user];
GRANT RECEIVE on dbo.ContactChangeMessages to [user];

The reason why I needed to go with sql sign in for users is I needed a unigue user to handle multiple clients. With a unique connection string per client - all clients would receive the change message. If the user id was the same only one client would get the change method.

Processing
© 2012 Microsoft. All rights reserved. Terms of Use | Trademarks | Privacy Statement
Page view tracker