﻿# SqlCacheDependency 如何套用到有多重表格關聯的情況

我們都知道 [SqlCacheDependency](http://msdn.microsoft.com/zh-tw/library/system.web.caching.sqlcachedependency.aspx) 有個很大的限制，就是一次只能用「單一表格」做判斷，如果你有個檢視表(View)或使用的 SQL 語法有 JOIN 兩個以上的表格，就無法利用 [SqlCacheDependency](http://msdn.microsoft.com/zh-tw/library/system.web.caching.sqlcachedependency.aspx) 幫你達成快取相依(CacheDependency)的設計，但我們大多的案子很少有「單一表格查詢」的狀況，以導致很多情境下無法使用 [SqlCacheDependency](http://msdn.microsoft.com/zh-tw/library/system.web.caching.sqlcachedependency.aspx) 感覺十分懊惱，但我們最近想出了新方法！

假設我們有兩個表格彼此相依，「父表格」為 NewsCategory，「子表格」為 NewsContent，而我們通常只會用 LINQ 取出 NewsCategory 的資料，至於 NewsContent 的資料真正要使用時可以直接用「點表示法」( dot notation ) 將資料取出，所以套用在 LINQ 的環境下，我們就可以很輕易的將 NewsCategory 註冊到 SQL Server 中（利用 [SqlCacheDependency](http://msdn.microsoft.com/zh-tw/library/system.web.caching.sqlcachedependency.aspx) ）。

而我們遇到主要的問題是，當 NewsContent 內容變更時，我們儲存在 Cache 中的 NewsCategory 資料並不會被 SQL Server 主動通知，所以被快取的資料永遠不會被更新。

要解決這個問題，就必須讓 NewsContent 更新資料時立即更新「父表格」NewsCategory 表格關連的那筆資料，至於更新什麼資料並不重要，只要有 UPDATE 過即可，例如：

```
UPDATE dbo.NewsCategory SET [ID]=[ID] WHERE ID=@ID
```

當啟用 [SqlCacheDependency](http://msdn.microsoft.com/zh-tw/library/system.web.caching.sqlcachedependency.aspx) 機制時，ASP.NET 會自動在 SQL Server 的資料庫中建立 一個 **AspNet\_SqlCacheTablesForChangeNotification** 表格，用來紀錄每一註冊表格資料的變化，其中有個欄位叫 changeId，只要註冊表格中的資料被異動就會自動 +1，由此可以看出當 NewsContent 異動時 NewsCategory 也跟著變化了，如下圖例：

![AspNet\_SqlCacheTablesForChangeNotification ](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/34adeb1f7558efffc97a-image.png "AspNet_SqlCacheTablesForChangeNotification ")

這樣的更新動作只要自己寫一組簡單的觸發程序(TRIGGER)就可以解決，範例程式如下：

```sql
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[NewsContent_Doggy_SqlCacheNotification_Trigger] 
   ON  [dbo].[NewsContent] 
   AFTER INSERT,DELETE,UPDATE
AS 
BEGIN
	SET NOCOUNT ON;

	Declare @ParentID uniqueidentifier

	 --insert
    IF (select count(*) from inserted) <> 0 and (select count(*) from deleted) = 0
	BEGIN
		DECLARE cur_Insert CURSOR FOR SELECT i.CategoryID FROM Inserted i
			
		OPEN cur_Insert
		
		FETCH NEXT FROM cur_Insert INTO @ParentID
		
		WHILE @@FETCH_STATUS = 0
		BEGIN
			Update dbo.NewsCategory
			Set [ID] = [ID]
			Where ID = @ParentID
			
			FETCH NEXT FROM cur_Insert INTO @ParentID
		END
		
		CLOSE cur_Insert

		DEALLOCATE cur_Insert
	END

	--update	
	IF (select count(*) from inserted) <> 0 and (select count(*) from deleted) <> 0
	BEGIN
		DECLARE cur_Update CURSOR FOR SELECT i.CategoryID FROM Inserted i
			
		OPEN cur_Update
		
		FETCH NEXT FROM cur_Update INTO @ParentID
		
		WHILE @@FETCH_STATUS = 0
		BEGIN
			Update dbo.NewsCategory
			Set [ID] = [ID]
			Where ID = @ParentID
			
			FETCH NEXT FROM cur_Update INTO @ParentID
		END
		
		CLOSE cur_Update

		DEALLOCATE cur_Update
	END
	
	--delete
	IF (select count(*) from inserted) = 0 and (select count(*) from deleted) <> 0 
	BEGIN
		DECLARE cur_Delete CURSOR FOR SELECT i.CategoryID FROM deleted i
			
		OPEN cur_Delete
		
		FETCH NEXT FROM cur_Delete INTO @ParentID
		
		WHILE @@FETCH_STATUS = 0
		BEGIN
			Update dbo.NewsCategory
			Set [ID] = [ID]
			Where ID = @ParentID
			
			FETCH NEXT FROM cur_Delete INTO @ParentID
		END
		
		CLOSE cur_Delete

		DEALLOCATE cur_Delete
	END

END
```

至於透過 LINQ 讀取 NewsCategory 抓取資料並快取後，為什麼連 NewsContent 的資料也會連帶被快取呢？這問題我仔細思考了一下終於理解。

原來是我們將透過 LINQ 讀取 NewsCategory 資料後 ( List<NewsCategory> ) 直接快取到 Cache 中，而這是一個簡單的 [POCO](http://en.wikipedia.org/wiki/Plain_Old_CLR_Object) 物件，所以當透過 LINQ 的「點表示法」將資料取出後，事實上所存取的物件還是在 Cache 中的物件，資料取回後寫入的地方正好也是 Cache 中的物件，因此資料也一併存在 Cache 中了，這真是一個美麗的意外阿。^\_^

這又再次證明採用 ORM 技術 (LINQ) 絕對沒錯的啦，這顆甜美的果實我們已經吃好久啦，至今還是依然香甜可口！:-)

**相關連結**

-   [SqlCacheDependency 類別 (System.Web.Caching)](http://msdn.microsoft.com/zh-tw/library/system.web.caching.sqlcachedependency.aspx)
-   [Plain Old CLR Object - Wikipedia, the free encyclopedia](http://en.wikipedia.org/wiki/Plain_Old_CLR_Object)
