版本:1.2 [2006-11-13]
本手冊提供 NBearV3 所支援之所有實體關係設計的完整參考,涵蓋一對一、一對多、多對多關聯以及自關聯的正向與反向引用等各種情況的設計方法。
LazyLoad 設為 false。ContainedAttribute 以實現級聯更新/刪除。演示代碼未包含此屬性,實務上請酌情添加,但正反向引用不可同時加上。本手冊所有類圖與代碼收錄於 NBearV3 最新原始碼套件(sf.net 下載)中的 tutorials\Entity_Relation_Manual 目錄,遇到疑問可直接參考該代碼。

分析:一對一主鍵關聯指兩個實體透過相同主鍵值關聯。典型範例為 User 與 UserProfile。UserProfile 不能脫離擁有相同主鍵的 User 存在。實際上,UserProfile.UserID 同時是其主鍵 (PK) 又是關聯到 User 的外鍵 (FK),且應對 User.ID 有引用完整性約束。
public interface User : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
[PkQuery(LazyLoad=true)]
UserProfile Profile { get; set; }
}
public interface UserProfile : Entity
{
[PrimaryKey]
[FriendKey(typeof(User))]
Guid UserID { get; set; }
string Content { get; set; }
[PkReverseQuery(LazyLoad = true)]
User User { get; set; }
}

分析:一對一外鍵關聯指一個實體透過自身的一個外鍵關聯另一實體。在關聯語義與資料庫映射上與一對多(外鍵)關聯非常相似,因此一對多中 FkQuery 與 FkReverseQuery 的解釋同樣適用。典型範例同為 User 與 UserProfile,不同處在於此時 UserProfile.ID 與 User.ID 無關,分別作為各自主鍵;UserProfile.UserID 為外鍵關聯到 User,並應對 User.ID 有引用完整性約束。
public interface User : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
[FkQuery("User", LazyLoad=true)]
UserProfile Profile { get; set; }
}
public interface UserProfile : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Content { get; set; }
[FkReverseQuery(LazyLoad=true)]
User User { get; set; }
}
註:標記在 User.Profile 上的 FkQuery 建構函式需一個參數,指定該外鍵查詢引用屬性對應的反向引用屬性為 UserProfile 實體的 User 屬性。

分析:一對一自關聯類似一對一外鍵關聯,差別僅在於關聯雙方為同一實體。
public interface Person : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
[FkReverseQuery(LazyLoad=true)]
[MappingName("MateID")]
[SerializationIgnore]
Person Mate { get; set; }
}
註 1:一對一自關聯可用單一屬性表示雙向引用,也可用正反兩個屬性。但必須至少將其中一個引用設為 LazyLoad=true,且至少一個設為 SerializationIgnore,以避免序列化死循環。
註 2:MappingName("MateID") 為可選屬性,顯式指定資料庫對應欄位名稱。若不指定,預設為「屬性名_主鍵名」(如 Mate_ID)。本手冊其他關聯屬性的 MappingName 含義相同。

分析:一對多關聯指一個實體透過被關聯實體的一個外鍵關聯多個被關聯實體集合。下圖示例表示 Team 可包含多個 User。
public interface Team : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
[FkQuery("Team", OrderBy="{Name} DESC", Contained=true, LazyLoad=true)]
User[] Users { get; set; }
}
public interface User : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
[FkReverseQuery(LazyLoad=true)]
Team Team { get; set; }
}
註 1:FkQuery 的 OrderBy 屬性可指定類 SQL 語法的排序規則,用於對關聯對象集合排序。
註 2:FkQuery 建構函式第一個參數指定被關聯實體中的關聯屬性。可為 FkReverseQuery 屬性(雙向引用),也可為外鍵屬性(單向引用)。若只要單向引用,可將 User.Team 改為簡單外鍵屬性,並修改建構函式參數為該外鍵屬性名稱:
public interface Team : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
[FkQuery("TeamID", Contained=true, LazyLoad=true)]
User[] Users { get; set; }
}
public interface User : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
[FriendKey(typeof(Team))]
Guid TeamID { get; set; }
}
註 3:FkQuery 可設 Contained 屬性,使被關聯對象隨主對象更新時自動級聯更新。

分析:一對多自關聯的定義方法與一般一對多關聯完全相同。
public interface Group : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
[FkReverseQuery(LazyLoad=true)]
[MappingName("ParentID")]
Group ParentGroup { get; set; }
[FkQuery("ParentGroup", LazyLoad=true)]
[SerializationIgnore]
Group[] ChildGroups { get; set; }
}
註:同樣必須至少將一個引用設為 LazyLoad=true,且至少一個設為 SerializationIgnore,避免序列化死循環。

分析:多對多關聯指一實體可關聯另一實體的多個對象,反之亦然。以 Group 與 User 為例:一個群組可有多個用戶,一用戶也可屬於多個群組。映射到資料庫時,必須有一個獨立的關聯表,對應此處的 UserGroup 關聯實體。
public interface Group : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
[ManyToManyQuery(typeof(UserGroup), LazyLoad=true)]
User[] Users { get; set; }
}
public interface User : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
[ManyToManyQuery(typeof(UserGroup), LazyLoad = true)]
Group[] Groups { get; set; }
}
[Relation]
public interface UserGroup : Entity
{
[RelationKey(typeof(User))]
Guid UserID { get; set; }
[RelationKey(typeof(Group))]
Guid GroupID { get; set; }
}
註 1:多對多關聯實體必須以 RelationAttribute 標記。其關聯雙方的屬性需用 RelationKeyAttribute 標記,建構函式參數(如 typeof(User)、typeof(Group))表示該屬性用於關聯到哪個實體。
註 2:在 Group 與 User 中,Users 與 Groups 屬性必須設 ManyToManyQueryAttribute,並以關聯實體 UserGroup 的類型作為建構函式參數。

分析:除常規關聯外,NBear 尚支援 CustomQuery 設定自定義查詢關聯屬性。它可在一般一對一、一對多、多對多查詢基礎上附加額外約束條件。不同處在於:自定義查詢關聯的對象不會被級聯更新。這些條件通常較簡單,但靈活運用可大幅減少業務類代碼。
下例包含三個實體:Team、Group、User。Team 與 User 為一對多,User 與 Group 為多對多,User 另包含數個 CustomQuery 屬性:
public interface Team : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
bool IsAvailable { get; set; }
[FkQuery("Team", OrderBy = "{Name} DESC", Contained = true, LazyLoad = true)]
User[] Users { get; set; }
}
public interface User : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
[ManyToManyQuery(typeof(UserGroup), LazyLoad = true)]
Group[] Groups { get; set; }
[FkReverseQuery(LazyLoad = true)]
Team Team { get; set; }
[CustomQuery("{LeaderName} = @Name AND {IsPublic} = 1")]
Group[] PublicLeadingGroups { get; set; }
[CustomQuery("{IsAvailable} = 1", OrderBy="{Name}")]
Team AvailableTeams { get; set; }
[CustomQuery("{IsHidden} = 1", RelationType=typeof(UserGroup), OrderBy="{ID} DESC")]
Group[] DivingGroups { get; set; }
}
public interface Group : Entity
{
[PrimaryKey]
Guid ID { get; set; }
string Name { get; set; }
string LeaderName { get; set; }
bool IsPublic { get; set; }
[ManyToManyQuery(typeof(UserGroup), LazyLoad = true)]
User[] Users { get; set; }
}
[Relation]
public interface UserGroup : Entity
{
[RelationKey(typeof(User))]
Guid UserID { get; set; }
[RelationKey(typeof(Group))]
Guid GroupID { get; set; }
bool IsHidden { get; set; }
}
說明:
User.PublicLeadingGroups:公開且 LeaderName 為該用戶名稱的所有群組。User.AvailableTeams:系統中所有 IsAvailable 為真團隊。User.DivingGroups:用戶所屬但處於「潛水」狀態(對群組其他成員不可見)的群組。其查詢條件 {IsHidden} = 1 中的 IsHidden 來自關聯實體 UserGroup,故需指定 RelationType=typeof(UserGroup)。亦可用 OrderBy 指定預設排序。正文結束