簡體   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