在 Windows 应用程序中使用 SqlDependency

以下方案显示如何在 Windows 窗体应用程序中使用 SqlDependency 类。为了演示通知的工作原理,您将创建两个应用程序:用于处理查询通知的监视程序和用于修改监视程序正在监视的数据的更新程序。

创建监视应用程序

监视应用程序是 Windows 窗体应用程序,将数据集从 SQL Server 2005 示例数据库 AdventureWorks 加载到 DataSet 中。DataSet 使用 SqlDataAdapter 对象填充,该对象绑定到 DataGridView 控件上。为了接收通知,创建一个 SqlDependency 对象并绑定到供 SqlDataAdapter 使用的 SqlCommand 对象。SqlDependency 对象公布单个事件 OnChange。为处理通知注册的处理程序执行必要的操作,以便从通知线程(一个线程池线程)切换到 UI 线程,然后重新注册,以接收通知。

创建监视应用程序

  1. 创建一个称为“Data Watcher”的新 Windows 应用程序项目。

  2. 如果使用的是 Visual Basic,请添加对 System.Data.dll 和 System.Xml.dll 的引用。

  3. 从“窗体设计器”中选择默认窗体。将“文本”属性更改为“Inventory Watcher”。

  4. 在窗体上添加一个 Label 控件。将标签控件放在窗体的底部。

  5. 在窗体左上方添加 ListBox 控件。将其大小调整为大约三行文本。

  6. 将 DataGridView 添加到 ListBox 控件下方。

  7. 在窗体上添加一个 Button 控件,并将该控件放置在 ListBox 控件的右侧。将其“文本”属性更改为“Get Data”。

  8. 打开窗体的类模块,并在文件顶部类定义的上方添加以下代码:

[Visual Basic]

Option Strict On

Imports System.ComponentModel

Imports System.Data

Imports System.Data.SqlClient

Imports System.Security.Permissions

[C#]

using System.Data.SqlClient;

using System.Security.Permissions;

  1. 在类的声明部分添加下列项:

[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;

  1. 在窗体中新建一个称为 CanRequestNotifications 方法。此方法将验证应用程序是否有权向服务器请求通知。

[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;

}

}

  1. 在窗体的 Load 事件中,使用 CanRequestNotifications 的返回值设置窗体的唯一一个按钮的 Enabled 属性。

[Visual Basic]

Button1.Enabled = CanRequestNotifications()

[C#]

Button1.Enabled = CanRequestNotifications();

  1. 添加两个帮助器方法 GetConnectionStringGetSQL。已定义的连接字符串使用集成安全性。您将需要验证所使用的帐户是否具有必要的数据库权限,并且示例数据库 AdventureWorks 是否启用了通知。有关更多信息,请参见使用查询通知时的特殊注意事项

[Visual Basic]

Private Function GetConnectionString() As String

' To avoid storing the connection string in your code,

' you can retrive it from a configuration file using the

' System.Configuration.ConfigurationSettings.AppSettings property.

' 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;"

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 using the

// System.Configuration.ConfigurationSettings.AppSettings property.

// 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;";

}

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;";

}

  1. 为了在服务器上的数据更改时接收通知,应用程序需要使用事件处理程序来匹配 OnChangeEventHandler 委托的签名。该过程需要捕获事件并从工作线程切换到 UI 线程。在窗体的模块中添加以下代码:

[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();

}

  1. 为了接收通知,应用程序需要在用于获取应用程序数据的 SqlCommand 对象中注册 SqlDependency 对象。添加一个称为 GetData 的方法,包含以下正文:

[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

OnChangedEventHandler(dependency_OnChange);

using (SqlDataAdapter adapter =

new SqlDataAdapter(command))

{

adapter.Fill(dataToWatch, tableName);

dataGridView1.DataSource = dataToWatch;

dataGridView1.DataMember = tableName;

}

}

  1. 为窗体上唯一一个按钮的 Click 事件添加一个事件处理程序,并将以下代码加入处理程序的正文:

[Visual Basic]

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()

[C#]

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();

  1. 在窗体的 FormClosed 事件中添加以下代码,以清理相关项和数据库连接:

[Visual Basic]

SqlDependency.Stop(GetConnectionString())

If connection IsNot Nothing Then

connection.Close()

End If

[C#]

SqlDependency.Stop(GetConnectionString());

if (connection != null)

{

connection.Close();

}

完成了监视应用程序后,将需要生成更新应用程序,然后同时运行这两个应用程序。

创建更新应用程序

更新应用程序提供一个简单的界面,用于修改监视应用程序正在监视的数据集。此示例中的更新程序也是 Windows 应用程序,但是任何类型的应用程序都可能使更新发生。

创建更新应用程序

  1. 创建一个称为“Data Updater”的新 Windows 应用程序项目。

  2. 如果使用的是 Visual Basic,请添加对 System.Data.dll 的引用。

  3. 从“窗体设计器”中选择默认窗体。在“属性”网格中,将“文本”属性更改为“Inventory Updater”。

  4. 在窗体上添加一个 Label 控件和一个 TextBox 控件。将标签的“文本”属性更改为 Product ID,并将文本框的“名称”属性更改为 txtProductID。

  5. 添加另一个标签和文本框。将标签的“文本”属性更改为 Quantity,并将文本框的“名称”属性更改为 txtQuantity。

  6. 添加一个 Button 控件,并将其“名称”属性设置为 Update。

  7. 在窗体的代码模块中,在文件的顶部添加以下代码:

[Visual Basic]

Option Strict On

Imports System.Data

Imports System.Data.SqlClient

[C#]

using System.Data.SqlClient;

  1. 在类的声明部分添加下列项:

[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;

  1. 在窗体上添加两个帮助器方法 GetConnectionStringGetSQL。这两个方法应类似如下:

[Visual Basic]

Private Function GetConnectionString() As String

' To avoid storing the connection string in your code,

' you can retrive it from a configuration file using the

' System.Configuration.ConfigurationSettings.AppSettings property.

' 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 "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 using the

// System.Configuration.ConfigurationSettings.AppSettings property.

// 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 "UPDATE Production.ProductInventory " +

"SET Production.ProductInventory.Quantity = @Quantity " +

"WHERE Production.ProductInventory.ProductID = @ProductID;";

}

  1. 在“Get Data”按钮的 Click 事件中添加以下代码:

[Visual Basic]

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 prm As New SqlParameter("@Quantity", SqlDbType.Int)

prm.Direction = ParameterDirection.Input

prm.DbType = DbType.Int32

command.Parameters.Add(prm)

prm = New SqlParameter("@ProductID", SqlDbType.Int)

prm.Direction = ParameterDirection.Input

prm.DbType = DbType.Int32

command.Parameters.Add(prm)

End If

command.Parameters("@Quantity").Value = quantity

command.Parameters("@ProductID").Value = productID

Dim rowsAffected As Integer = command.ExecuteNonQuery()

MessageBox.Show(rowsAffected & " records updated.", "Update")

[C#]

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 prm = new

SqlParameter("@Quantity", SqlDbType.Int);

prm.Direction = ParameterDirection.Input;

prm.DbType = DbType.Int32;

command.Parameters.Add(prm);

prm = new SqlParameter("@ProductID", SqlDbType.Int);

prm.Direction = ParameterDirection.Input;

prm.DbType = DbType.Int32;

command.Parameters.Add(prm);

}

command.Parameters["@Quantity"].Value = quantity;

command.Parameters["@ProductID"].Value = productID;

int rowsAffected = command.ExecuteNonQuery();

MessageBox.Show(rowsAffected + " records updated.", "Update");

  1. 最后,在窗体的 FormClosed 事件处理程序中添加以下代码:

[Visual Basic]

If connection IsNot Nothing Then

connection.Close()

End If

[C#]

if (connection != null)

{

connection.Close();

}

编译并运行两个应用程序。单击监视应用程序上的“Get Data”后,应在应用程序的网格中填充数据,并注册一个通知请求。检查网格中的数据,注意库存为零的产品的产品 ID。然后,使用更新应用程序输入产品 ID 和新数量 1,再单击“Update”。应看到在更新程序更改了服务器上的数据之后,监视应用程序立即自动进行更新。观察 Listbox 控件显示有关更新的信息。对其他数量和产品重复该过程。

请参见

其他资源

使用查询通知

标记 :


Page view tracker