如何使用 c# 监控 SQL Server 表更改?

Posted

技术标签:

【中文标题】如何使用 c# 监控 SQL Server 表更改?【英文标题】:How to monitor SQL Server table changes by using c#? 【发布时间】:2021-10-17 05:54:54 【问题描述】:

我有多个应用程序访问同一个数据库,如果其中一个应用程序更改某个表中的任何内容(更新、插入),我需要得到通知。

数据库和应用不在同一台服务器上。

【问题讨论】:

您需要什么样的通知?即时?您需要通知应用程序,还是需要向您发送电子邮件?您真的需要收到通知,还是只想跟踪这些更改? 我只需要知道是否有任何其他应用程序更新或插入任何数据,我不需要数据本身只是该表有新更改的标志。抱歉迟到了,我不知道答案这么快 【参考方案1】:

您可以使用SqlDependency Class。它的预期用途主要用于 ASP.NET 页面(客户端通知数量较少)。

ALTER DATABASE UrDb SET ENABLE_BROKER

实现OnChange 事件以获得通知:

void OnChange(object sender, SqlNotificationEventArgs e)

在代码中:

SqlCommand cmd = ...
cmd.Notification = null;

SqlDependency dependency = new SqlDependency(cmd);

dependency.OnChange += OnChange;

它使用Service Broker(基于消息的通信平台)从数据库引擎接收消息。

【讨论】:

@jaroslav jandek,嗨。你知道除了 sqldependency 之外的另一种方式吗?我对 sqldependency 有疑问,因为它受到很多限制,例如 OUTER JOIN;我在几乎所有的 sql 查询中都使用它! @M_Mogharrabi 通知是使用不能与外部连接一起使用的索引完成的。您必须手动将外部联接作为单独的查询进行联接。在大多数情况下,我会尽量避免这种情况。 @Kiquenet SB 的性能在这里不是问题。不过,查询通知会对数据库性能产生重大影响。特别是如果有很多通知(在我的回答中提到)。如果是这种情况,您可能会更好地使用轮询、SOA... 我使用 SqlSependency 来触发数据库更改以向客户端显示推送通知,但最近我们转移到 SQL Azure,它不支持SqlSependency 所以有没有比this 更好的方法来获取通知SQL Azure 数据何时更改或插入新数据时? @stom 没有替代品,AFAIK。如果您控制数据输入,您可以使用 SignalR 或类似技术轻松通知...【参考方案2】:

为了完整起见,还有一些其他解决方案(在我看来)比依赖 SqlDependency(和 SqlTableDependency)类的解决方案更正统。 SqlDependency 最初旨在使刷新分布式 Web 服务器缓存更容易,因此与将其设计为事件生产者相比,它的构建要求不同。

大致有四个选项,其中一些尚未在此处介绍:

更改跟踪 疾病预防控制中心 队列触发器 CLR

更改跟踪

来源:https://docs.microsoft.com/en-us/sql/relational-databases/track-changes/about-change-tracking-sql-server

更改跟踪是 SQL Server 中的一种轻量级通知机制。基本上,数据库范围的版本号会随着任何数据的每次更改而增加。然后将版本号写入更改跟踪表,其中包含一个位掩码,包括已更改的列的名称。请注意,实际更改不会保留。通知仅包含特定数据实体已更改的信息。此外,由于更改表版本控制是累积的,因此不会保留有关单个项目的更改通知,并且会被较新的通知覆盖。这意味着如果一个实体更改了两次,更改跟踪将只知道最近的更改。

为了在 c# 中捕获这些变化,必须使用轮询。可以轮询更改跟踪表并检查每个更改以查看是否感兴趣。如果感兴趣,则有必要然后直接进入数据以检索当前状态。

变更数据捕获

来源:https://technet.microsoft.com/en-us/library/bb522489(v=sql.105).aspx

变更数据捕获 (CDC) 比变更跟踪更强大,但成本最高。变更数据捕获将根据监控数据库日志跟踪和通知变更。因此,CDC 可以访问已更改的实际数据,并记录所有单独的更改。

与变化跟踪类似,为了在c#中捕捉这些变化,必须使用轮询。但是,在 CDC 的情况下,轮询信息将包含更改详细信息,因此不必返回数据本身。

触发队列

来源:https://code.msdn.microsoft.com/Service-Broker-Message-e81c4316

此技术依赖于需要通知的表上的触发器。每个更改都会触发一个触发器,触发器会将这些信息写入服务代理队列。然后可以使用 Service Broker 消息处理器(上面链接中的示例)通过 C# 连接队列。

与更改跟踪或 CDC 不同,队列触发器不依赖轮询,从而提供实时事件。

CLR

这是我见过的一种技术,但我不推荐它。任何依赖 CLR 与外部通信的解决方案充其量都是一种 hack。 CLR 旨在通过利用 C# 来更轻松地编写复杂的数据处理代码。它并非旨在连接外部依赖项,如消息传递库。此外,CLR 绑定操作可能会以不可预知的方式在集群环境中中断。

也就是说,设置起来相当简单,因为您需要做的就是向 CLR 注册消息程序集,然后您可以使用触发器或 SQL 作业进行调用。

总结...

让我感到惊讶的是,Microsoft 坚决拒绝解决这个问题。从数据库到代码的事件应该是数据库产品的内置特性。考虑到 Oracle Advanced Queuing 与 ODP.net MessageAvailable 事件相结合,为 C# 提供了可靠的数据库事件处理超过10 年前,这对 MS 来说是可悲的。

这样做的结果是,针对这个问题列出的解决方案都不是很好。它们都存在技术缺陷并且设置成本很高。微软,如果你在听,请解决这个令人遗憾的状况。

【讨论】:

【参考方案3】:

一般来说,你会使用Service Broker

即触发器 -> 队列 -> 应用程序

在看到其他答案后编辑:

仅供参考:“查询通知”基于服务代理构建

编辑2:

更多链接

Event Notification/Service broker Service Broker team

【讨论】:

我已经使用 SqlSependency 来触发数据库更改以向客户端显示推送通知,但最近我们转移到 SQL Azure,它不支持SqlSependency 所以有没有比this 更好的方法来获取通知SQL Azure 数据何时更改或插入新数据时?【参考方案4】:

使用 SqlTableDependency。它是一个 C# 组件,当记录发生更改时引发事件。 您可以在以下位置找到其他详细信息:https://github.com/christiandelbianco/monitor-table-change-with-sqltabledependency

它类似于 .NET SqlDependency,除了 SqlTableDependency 引发包含修改/删除或更新的数据库表值的事件:

string conString = "data source=.;initial catalog=myDB;integrated security=True";

using(var tableDependency = new SqlTableDependency<Customers>(conString))

    tableDependency.OnChanged += TableDependency_Changed;
    tableDependency.Start();

    Console.WriteLine("Waiting for receiving notifications...");
    Console.WriteLine("Press a key to stop");
    Console.ReadKey();

...
...
void TableDependency_Changed(object sender, RecordChangedEventArgs<Customers> e)

    if (e.ChangeType != ChangeType.None)
    
        var changedEntity = e.Entity;
        Console.WriteLine("DML operation: " + e.ChangeType);
        Console.WriteLine("ID: " + changedEntity.Id);
        Console.WriteLine("Name: " + changedEntity.Name);
        Console.WriteLine("Surname: " + changedEntity.Surname);
    

【讨论】:

【参考方案5】:

小心使用 SqlDependency 类 - 它有 problems 内存泄漏。

只需使用跨平台、.NET 3.5、.NET Core 兼容的开源解决方案 - SqlDependencyEx。您可以获取通知以及更改的数据(您可以通过通知事件对象中的属性访问它)。您还可以单独或一起执行 DELETE\UPDATE\INSERT 操作。

这是一个使用SqlDependencyEx的简单示例:

int changesReceived = 0;
using (SqlDependencyEx sqlDependency = new SqlDependencyEx(
          TEST_CONNECTION_STRING, TEST_DATABASE_NAME, TEST_TABLE_NAME)) 

    sqlDependency.TableChanged += (o, e) => changesReceived++;
    sqlDependency.Start();

    // Make table changes.
    MakeTableInsertDeleteChanges(changesCount);

    // Wait a little bit to receive all changes.
    Thread.Sleep(1000);


Assert.AreEqual(changesCount, changesReceived);

请点击链接了解详情。该组件在许多企业级应用程序中进行了测试,并被证明是可靠的。希望这会有所帮助。

【讨论】:

与Sql Express兼容吗? 可以,兼容【参考方案6】:

SqlDependency 不监视它监视您指定的 SqlCommand 的数据库,因此,如果您试图在 1 个项目中将值插入数据库并在另一个项目中捕获该事件,它将无法工作,因为该事件来自来自 1º 项目的 SqlCommand 而不是数据库,因为当您创建 SqlDependency 时,您会将其链接到 SqlCommand,并且仅当使用该项目中的该命令时,它才会创建 Change 事件。

【讨论】:

这实际上是不正确的。即使您在 Management Studio 中插入值,SqlDependency 也有效。但是这个类有很多问题,比如内存泄漏。有关详细信息,请参阅下面的答案。 @KayLee @dyatchenko,感谢您的意见。我正在使用在这篇文章的一个答案中提到的 SqlTableDependency。我现在很忙,但稍后会看看内存问题,当然......【参考方案7】:

从 SQL Server 2005 开始,您可以选择使用 Query Notifications,ADO.NET 可以利用它,请参阅 http://msdn.microsoft.com/en-us/library/t9x04ed2.aspx

【讨论】:

【参考方案8】:

一路上看起来都是糟糕的架构。您还没有指定需要通知的应用程序类型(网络应用程序/控制台应用程序/winforms/服务等)

不过,要回答您的问题,有多种方法可以解决这个问题。你可以使用:

1) 如果您只是想确保第二个应用的下一组更新与第一个应用的更新不冲突,则使用时间戳

2) sql 依赖对象 - 请参阅http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldependency.aspx 了解更多信息

3) 一个自定义推送通知服务,多个客户端(web / winform / service)可以订阅并获得更改通知

简而言之,您需要根据您的通知要求的复杂程度以及您需要使用它们的目的来使用最简单、最简单和最便宜(就工作量而言)的解决方案。如果您的唯一要求是简单的数据并发,请不要尝试构建过于复杂的通知系统(在这种情况下,请选择基于简单时间戳的解决方案)

【讨论】:

出于好奇,您能否澄清一下这里的“糟糕架构”是什么?【参考方案9】:

另一种非常简单的监控表的方法是表版本控制。该系统已被证明可以在 DNS 同步等结构中工作。为了使其工作,您创建一个包含表名称和表版本的表decimalbigint.在您需要监控的每个表中,在插入、更新和删除时创建触发器,这将在执行时在版本控制表中增加适当的表版本。如果您希望任何受监视的表经常更改,则需要为版本重用提供服务。最后,在您的应用程序中,每次查询受监控的表时,您也会查询其版本并存储它。当您从您的应用程序中更改监控表时,您首先查询其当前版本并仅在版本未更改时处理更改。您可以在 sql server 上存储 proc 为您完成这项工作。 这是非常简单但经过验证的可靠解决方案。它具有特定的功能用途(以确保数据一致性)并且资源较少(您不会引发您不会关注的代理事件),但需要应用程序主动检查更改而不是被动地等待事件发生。

【讨论】:

我认为这会在包含给定表的版本的行周围产生锁定瓶颈。【参考方案10】:

这并不完全是一个通知,但在标题中你说监视器,这可以适合这种情况。

使用 SQL Server 时间戳列可以让您轻松查看查询之间的任何更改(仍然存在)。

在我看来,SQL Server 时间戳列类型的命名很糟糕,因为它根本与时间无关,它是一个数据库范围的值,在任何插入或更新时都会自动递增。您可以在您之后的表中选择 Max(timestamp) 或从您刚刚插入的行中返回时间戳,然后只需选择 where timestamp > storedTimestamp,这将为您提供在这些时间之间已更新或插入的所有结果。

由于它也是一个数据库范围的值,因此您可以使用存储的时间戳来检查自上次检查/更新存储的时间戳以来是否有任何表已写入数据。

【讨论】:

【参考方案11】:

1-以TestNotification的名称创建新数据库

2-将新表添加到Customers字段的名称:IdNameFamily

3-你应该启用ServiceBroker

4-在sql中运行这段代码

ALTER DATABASE [TestNotification] SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE

5-创建新项目c# consoleApp

6- 在nuget 中安装SqlTableDependency

7- 以Customer 的名称创建类

public class Customer

    public int Id  get; set; 
    public string Name  get; set; 
    public string Family  get; set; 

8-在Program.cs写下这段代码

        static void Main(string[] args)
        
            var connectionString = "data source=.;initial catalog=TestNotification;integrated security=true;";
            using (var tableDependecy = new SqlTableDependency<Customer>(connectionString, "Customers"))
            
                tableDependecy.OnChanged += TableDependency_Changed;
                tableDependecy.OnError += TableDependency_OnError;

                tableDependecy.Start();

                Console.WriteLine("Waiting");

                Console.ReadKey();
                tableDependecy.Stop();
            
        

        static void TableDependency_Changed(object sender, RecordChangedEventArgs<Customer> e)
        
            Console.WriteLine(Environment.NewLine);
            if (e.ChangeType != ChangeType.None)
            
                var changeEntity = e.Entity;
                Console.WriteLine("ChangeType: " + e.ChangeType);
                Console.WriteLine("Id: " + changeEntity.Id);
                Console.WriteLine("Name: " + changeEntity.Name);
                Console.WriteLine("Id: " + changeEntity.Family);
                Console.WriteLine(Environment.NewLine);
            
        

        static void TableDependency_OnError(object sender, ErrorEventArgs e)
        
            Console.WriteLine(e.Message);
        

【讨论】:

以上是关于如何使用 c# 监控 SQL Server 表更改?的主要内容,如果未能解决你的问题,请参考以下文章

跟踪在 C# 应用程序或 SQL Server 中对 SQL Server 进行更改的人员

如何在 C# 控制台应用程序中使用 XmlTextReader 将 XML 数据插入 SQL Server 表?

如何阻止 SQL Server CE 3.5 更改跟踪表无限增长?

使用 c# 将 MS Access 表数据添加到 SQL Server 表中

如何更改 SQL Server 中的前缀表?

sqlserver2008 监控某个表的改动情况