繁体   English   中英

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

[英]How to monitor SQL Server table changes by using c#?

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

数据库和应用程序不在同一台服务器中。

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

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

  • 更改跟踪
  • CDC
  • 队列触发器
  • 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 的情况下,轮询的信息将包含更改的详细信息,因此不必返回数据本身。

队列触发器

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

与更改跟踪或 CDC 不同,队列的触发器不依赖于轮询,因此提供了实时事件。

CLR

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

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

总之...

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

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

您可以使用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 (基于消息的通信平台)从数据库引擎接收消息。

通常,您会使用Service Broker

那是触发器->队列->应用程序

编辑,看到其他答案后:

仅供参考:“查询通知”建立在服务代理之上

编辑2:

更多链接

使用 SqlTableDependency。 当记录发生更改时,这是 ac# 组件引发事件。 您可以在以下位置找到其他详细信息: 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);
    }
}

小心使用SqlDependency类 - 它存在内存泄漏问题。

只需使用跨平台、.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);

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

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

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

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

但是,要回答您的问题,有多种方法可以解决此问题。 你可以使用:

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

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

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

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM