当数据库更新时,我该如何通知我的程序?

10

我有一个用C#编写的程序,用于查询SQL Server数据库中的某些值。

目前该应用程序每分钟查询一次数据库以确保表格是最新的。

我希望能够在仅当数据库发生更改/更新时才执行查询。如何通知我的程序数据库已被更改?

谢谢

5个回答

9

轮询数据库并不是一个很优雅的解决方案。

ADO.NET 中的 SqlDependency 将在您的情况下非常有用。它不使用轮询,而是使用通知机制。这些通知由您的数据库中的 Service Broker 提供,因此您需要在数据库中启用此服务。当指定的表更改(更新、删除、插入)时,OnChange 事件将被触发。

以下是如何使用 SqlDependency 的示例:

void Initialization()
{
    // Create a dependency connection.
    SqlDependency.Start(connectionString, queueName);
}

void SomeMethod()
{
    // Assume connection is an open SqlConnection.

    // Create a new SqlCommand object.
    using (SqlCommand command=new SqlCommand(
        "SELECT ShipperID, CompanyName, Phone FROM dbo.Shippers", 
        connection))
    {

        // Create a dependency and associate it with the SqlCommand.
        SqlDependency dependency=new SqlDependency(command);
        // Maintain the refence in a class member.

        // Subscribe to the SqlDependency event.
        dependency.OnChange+=new
           OnChangeEventHandler(OnDependencyChange);

        // Execute the command.
        using (SqlDataReader reader = command.ExecuteReader())
        {
            // Process the DataReader.
        }
    }
}

// Handler method
void OnDependencyChange(object sender, 
   SqlNotificationEventArgs e )
{
  // Handle the event (for example, invalidate this cache entry).
}

void Termination()
{
    // Release the dependency.
    SqlDependency.Stop(connectionString, queueName);
}

来自 http://msdn.microsoft.com/en-us/library/62xk7953.aspx

以下是如何启用Service Broker的方法(请注意,您需要对数据库进行排他性操作才能这样做 - 最好在重新启动SQL服务器后执行此操作):http://blogs.sftsrc.com/stuart/archive/2007/06/13/42.aspx(链接已损坏)

可能的替代链接:http://technet.microsoft.com/en-us/library/ms166086(v=sql.105).aspx


2
启用服务代理的链接已损坏。http://technet.microsoft.com/zh-cn/library/ms166086(v=sql.105).aspx - Tiele Declercq

4
为了在某个记录更新时收到通知,避免应用程序查询表格,您可以使用TableDependency组件(在您的特定情况下使用SqlTableDependency)。以下是一个例子:
public partial class Window1 : Window
{
    private IList<Stock> _stocks;
    private readonly string _connectionString = 
        "data source=.;initial catalog=myDB;integrated security=True";
    private readonly SqlTableDependency<Stock> _dependency;

    public Window1()
    {
        this.InitializeComponent();
        this.McDataGrid.ItemsSource = LoadCollectionData();
        this.Closing += Window1_Closing;

        var mapper = new ModelToTableMapper<Stock>();
        mapper.AddMapping(model => model.Symbol, "Code");

        _dependency = new SqlTableDependency<Stock>(_connectionString, "Stocks", mapper);
        _dependency.OnChanged += _dependency_OnChanged;
        _dependency.OnError += _dependency_OnError;
        _dependency.Start();
    }

    private void Window1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        _dependency.Stop();
    }

    private void _dependency_OnError(object sender, TableDependency.EventArgs.ErrorEventArgs e)
    {
        throw e.Error;
    }

    private void _dependency_OnChanged(
        object sender, 
        TableDependency.EventArgs.RecordChangedEventArgs<Stock> e)
    {
        if (_stocks != null)
        {
            if (e.ChangeType != ChangeType.None)
            {
                switch (e.ChangeType)
                {
                    case ChangeType.Delete:
                        _stocks.Remove(_stocks.FirstOrDefault(c => c.Symbol == e.Entity.Symbol));
                        break;
                    case ChangeType.Insert:
                        _stocks.Add(e.Entity);
                        break;
                    case ChangeType.Update:
                        var customerIndex = _stocks.IndexOf(
                                _stocks.FirstOrDefault(c => c.Symbol == e.Entity.Symbol));
                        if (customerIndex >= 0) _stocks[customerIndex] = e.Entity;
                        break;
                }

                this.McDataGrid.Dispatcher.Invoke(DispatcherPriority.Background, new Action(() =>
                {
                    this.McDataGrid.Items.Refresh();
                }));
            }
        }
    }

    private IEnumerable<Stock> LoadCollectionData()
    {
        _stocks = new List<Stock>();

        using (var sqlConnection = new SqlConnection(_connectionString))
        {
            sqlConnection.Open();
            using (var sqlCommand = sqlConnection.CreateCommand())
            {
                sqlCommand.CommandText = "SELECT * FROM [Stocks]";

                using (var sqlDataReader = sqlCommand.ExecuteReader())
                {
                    while (sqlDataReader.Read())
                    {
                        var code = sqlDataReader
                                .GetString(sqlDataReader.GetOrdinal("Code"));
                        var name = sqlDataReader
                                .GetString(sqlDataReader.GetOrdinal("Name"));
                        var price = sqlDataReader
                                .GetDecimal(sqlDataReader.GetOrdinal("Price"));

                        _stocks.Add(new Stock { Symbol = code, Name = name, Price = price });
                    }
                }
            }
        }

        return _stocks;
    }

当表格进行每次INSERT UPDATE或DELETE操作时,事件处理程序都会被触发,报告您修改后的值。因此,如果您想保持C# Datatable的最新状态,只需从事件处理程序获取最新数据即可。


谢谢,看起来需要根据每个情况进行修改,但是代码很好。 - Kay Lee

4

如果您使用的是 SQL Server 2005 或更高版本,则可以考虑使用 SqlDependency 对象。

它表示应用程序和 SQL Server 2005 实例之间的查询通知依赖关系。

应用程序可以创建 SqlDependency 对象,并通过 OnChangeEventHandler 事件处理程序注册以接收通知。

请参阅 MSDN 上的此链接获取更多信息。

但是,请注意 MS 对其使用的限制。建议先有缓存层,然后与该层协调使用 SQLDependency。

SqlDependency 是专为 ASP.NET 或中间层服务设计的,其中相对较少的服务器对数据库有活动依赖项。它不适用于客户端应用程序,在该应用程序中,数百或数千个客户端计算机会为单个数据库服务器设置 SqlDependency 对象。


1
我想要做的是只有在数据库被更改/更新时才进行查询。如何通知我的程序数据库中的某些内容已经更新?
目前没有办法让数据库向应用程序推送通知。应用程序需要轮询数据库以检查更新,然后适当地处理更新。

-2
如果你所说的“数据库更新”是指任何应用程序的更新,那么很遗憾,这是不可行的。
但是,如果你指的是你的应用程序所做的更改,那就很容易了:每次更新数据库时,触发一个事件并让处理程序响应该事件。

1
SqlDependancy正是它的设计初衷。 - Kristian Williams

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接