在 Rafy 領域實體框架中,對自關聯的實體結構做了特殊的處理,下面對這一功能進行講解。
場景
在開發數據庫應用程序時,往往會遇到自關聯表的場景。例如,分類信息、組織架構中的部門、文件夾信息等,都是不限制層級的。如下圖中操作系統的文件夾:
在開發這類程序時,往往是設計一張表,表中的一個可空的外鍵直接引用這張表本身。對應的實體如下圖:
而針對這樣的場景,許多ORM框架都不做默認的處理,開發者往往每次都要做重復的工作:建立類似結構的表,編寫關系處理代碼,編寫查詢代碼……而這種場景經常會出現,所以 Rafy 實體框架中,默認就支持了樹型實體的一系列功能,來降低重復勞動。
功能及使用說明
在 Rafy 中的樹型實體功能,只需開發者使用一行代碼為指定的實體打開這個功能,框架會自動完成以下工作:
- 自動添加實體的自引用關系。自動生成數據庫自關聯表。
- 自動維護樹節點的 TreeIndex 索引。
- 自帶多個查詢,用於查詢樹節點。
- 查詢結果自動轉變為樹的結構。
- 支持樹節點的按需加載。
下面,將逐一進行講解。
打開樹型實體功能
開發者只需使用一行代碼即可讓指定的實體轉變為樹型實體。在指定實體的配置代碼中,添加下面這行代碼即可:
自動添加實體的自引用關系
實體基類上已經默認帶有以下幾個屬性,來表達樹節點之間的關系。
當某個實體類型被配置為樹型實體時,這幾個屬性才會有意義。
SupportTree:指示該實體是否為樹型實體。
TreeIndex:樹節點的編碼、索引。此屬性會映射為數據庫中的字段。
TreePId:該樹節點的父節點的 Id。此屬性會映射為數據庫中的字段。
TreeParent:該樹節點的父節點實體。
TreeChildren:該樹節點的所有子節點集合。
自動生成數據庫自關聯表
運行程序后,該實體對應的表將會自動添加兩個字段:TreeIndex、TreePId,如下圖:
自動維護樹節點的 TreeIndex 索引
TreeIndex 是樹結點的系統編號,由框架自動維護。下圖顯示了一個正在使用的樹的 TreeIndex 的格式:
這個屬性不但可以用於顯示,更重要的是它是樹型實體大量功能的結構基礎。例如,當查詢某個節點下的所有節點時,就是通過 TreeIndex 來進行模糊匹配的。所以這個屬性的值非常重要,只能由框架來自行維護,而不能由開發者來設置。
開發者可以通過 TreeParent、TreeChildren、TreePId 等屬性來變更節點與節點之間的父子關系,這時,對應的節點的 TreeIndex 則會同時自動變更。
樹結構的表示
樹的結構非常重要,我手畫了張草圖來表示:
主要由三個類型構成整個樹:EntityList、Entity、EntityTreeChildren。這個結構可以表示完整的一棵樹,也可以表示部分樹。其中,EntityList 用於存儲樹的根節點(如果是部分樹,則表示最上層節點);Entity 表示樹中的每一個節點;EntityTreeChildren 集合則表示某個節點下的子節點。
另外,EntityTreeChildren 集合可以按需加載。當它還沒有進行加載時,遍歷整個樹只能遍歷到當前已經在內存中的樹節點。例如,上圖中,Root3的子節點沒有被加載,1.2.2 的子節點也沒有被加載。
那么,如何加載還沒有加載到內存中的節點呢?這需要使用到 ITreeComponent 接口中的 LoadAllNodes 方法。EntityList、Entity、EntityTreeChildren 這三個類型都實現了 ITreeComponent 接口,下面是這個接口的定義:
另外,可以使用其中的 EachNode 方法來以深度優先的算法遍歷整棵樹。
自帶多個查詢,用於查詢樹節點
實體倉庫中帶有許多查詢方法,其中一些是專門為樹型實體設計的:
- GetTreeRoots:查詢所有的根節點。
- GetByTreePId:查找指定樹節點的直接子節點。
- GetByTreeParentIndex:遞歸查找指定父索引號的節點下的所有子節點。
- LoadAllTreeParents:遞歸加載某個節點的所有父節點。使用此方法后,指定節點的父節點將被賦值到它的 TreeParent 屬性上。
- GetAllTreeParents:獲取指定索引對應的樹節點的所有父節點。 查詢出的父節點同樣以一個部分樹的形式返回。
另外,一些非樹實體的查詢方法,對於樹型實體也是可用的。如 GetAll、GetByParentId 等。但是也會有所區別,例如 GetAll 方法在查詢非樹實體時,查詢出的實體列表中包含所有的實體;但是在查詢樹型實體時,結果會按照樹的結構來進行加載,即列表中只會有根節點,其它節點則分別在根節點的下級節點中。
同時,這些查詢往往支持是否使用貪婪加載的參數。以 GetTreeRoots 方法舉例,它的接口是這樣的:public EntityList GetTreeRoots(EagerLoadOptions eagerLoad = null); 。它在默認情況下只返回根節點,而根節點中的子節點是沒有被加載的。但是,我們可以通過參數中的 eagerLoad 來指定,在加載根節點的同時,把所有的子節點都加載上。
以上只是對一些接口做一些必要的解釋,具體的使用方法及其它的接口,請參照注釋及源碼中的單元測試。
限制
說了上面這么多自帶的功能,但是 Rafy 中樹型實體的設計也有這的限制:一個樹型實體類型對應的數據表中,只能存儲一棵樹。樹中的所有節點的 TreeIndex 都必須是唯一的。
好了,鑒於篇幅,這篇文章只是簡單地講解了樹型實體中的重點概念及功能,並沒有深入說明。這是因為,在使用的過程中你會發現,一般情況下用起來非常容易,只需要打開樹型實體功能,並調用想要的查詢就可以了,用不到特別復雜的 API。如果確實需要深入了解,那么在理解了整個樹的結構設計后,再結合幫助、注釋以及源碼中的單元測試,相信也會比較簡單。