版本
1.2 [2006-11-13]
簡介
本手冊演示NBearV3支持的所有實體關系設計的完全參考。包括1對1,1對多,多對多關聯以及自關聯的正向、反向引用時的所有情況的設計方法。
注1:本手冊並不討論繼承關系。因為,繼承關系自然映射到設計實體接口的繼承,無需太多額外討論。
注2:所有的關聯在演示中都包含了正向和反向的可讀寫引用,在實際的項目中,一般並不總是需要同時有正向和反向引用,可以只在一個方向包含引用,或一方包含引用,另一方只包含一個外鍵ID,也可以只包含只讀的引用。並且,在雙向引用時,絕對不能同時設置為雙向的LazyLoad=false。
注3:所有關聯關系中的正向或反向引用的屬性,根據需要,可以添加ContainedAttribute標識,以實現屬性和包含屬性的實體的級聯更新/刪除。在本文演示的所有引用中都沒有包含ContainedAttribute,實際項目中請注意添加,但是不要同時在正向和反向引用屬性中添加ContainedAttribute。
注4:在閱讀本文之前,建議讀者先閱讀《NBearV3 Step by Step教程——ORM篇》以掌握NBearV3中有關ORM的基本知識。
代碼
本手冊演示的所有類圖和代碼,包含於可以從sf.net下載的NBearV3最新源碼zip包中的tutorials\Entity_Relation_Manual目錄中。因此,在使用本手冊的過程中如有任何疑問,可以直接參考這些代碼。
正文
一、1對1主鍵關聯
分析:一對一主鍵關聯指的是兩個實體通過相同的主鍵進行關聯。典型的關聯關系如圖中的User和UserProfile的關聯。其中,UserProfile不能脫離具有相同主鍵值的User存在。因此,實際上,對於UserProfile來說,它的屬性UserID既是它的PK,又是一個關聯到User的FK,且UserProfile.UserID應該對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;
}
}
二、1對1外鍵關聯
分析:一對一外鍵關聯指的是一個實體通過自己的一個外鍵和另一個實體進行關聯。一對一外鍵關聯無論在關聯語義還是數據庫映射上都和1對多(外鍵)關聯非常類似,所以,對於1對多(外鍵)關聯中FkQuery和FkReverseQuery的解釋同樣適用於1對1外鍵關聯的情形。典型的關聯關系如圖中的User和UserProfile的關聯。這裡和前面的1對1主鍵關聯的不同在於:此時,UserProfile.ID和User.ID並不相關,分別只作為各自實體的主鍵,UserProfile.UserID是一個外鍵,UserProfile通過這個外鍵關聯到User實體。UserProfile.UserID應該對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屬性。
三、1對1自關聯
分析:1對1自關聯類似1對1外鍵關聯,區別僅僅在於,關聯的雙方是同一個實體。
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:注意Person.Mate中的MappingName(“MateID”)。該Attribute是可選的。這裡顯式指定為MateID,表示Person實體映射到數據庫表時,需要創建的用於關聯Mate屬性的字段名稱為MateID。如果不指定,則默認值一般為”屬性名_主鍵名”,以這裡為例,如果不指定這個MappingName,則這個字段的名稱為Mate_ID,因為屬性名為Mate,關聯類型Person的主鍵字段名為ID。本手冊中其他為關聯屬性指定的MappingName的含義都同該注解。
四、1對多關聯
分析:一對多關聯指一個實體通過被關聯實體的一個外鍵關聯多個被關聯實體的集合。舉例來說,下面的圖表示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語法的排序規則,用於對一組關聯的對象進行排序。本手冊中的其他OrderBy屬性的含義總是和這裡的含義相同。
注2:FkQuery的構造函數的第一個參數(如Team.Users屬性中OneToManyQuery構造函數的第一個參數Team)指定對應的被關聯實體中的關聯屬性。這個關聯屬性可以是一個FkReverseQuery屬性(即雙向引用),也可以是一個外鍵屬性(即只在One實體包含到Many實體的引用)。本例中演示的是雙向引用,如果只想包含單向引用的話,可以將User.Team屬性改為一個簡單的外鍵屬性,並將OneToManyQuery構造函數的第一個參數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屬性,使得被關聯對象元素隨主對象更新時自動級聯更新。本手冊中其他Attribute的Contained屬性的含義,總是和這裡的Contained的含義相同。
五、1對多自關聯
分析:1對多自關聯和普通的1對多關聯的定義方法完全相同。
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的關聯為例,Group中可以有多個User,User也可以在多個Group中。在映射多對多關聯的的實體到數據庫表時,我們總是需要有一個獨立的關聯表,對應這裡的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標識,RelationKeyAttribute的唯一一個構造函數參數,如這個裡的typeof(User)和typeof(Group)分別表示,這兩個屬性用於關聯到這兩個實體。
注2:在實體Group和User中,我們必須為屬性Users和Groups設置ManyToManyQueryAttribute,並且用關聯實體UserGroup的類型值作為ManyToManyQuery的唯一的構造函數參數。
七、自定義關聯
分析:除了以上常規的關聯關系之外,NBear還支持使用CustomQuery設置自定義查詢關聯屬性。CustomQuery可以實現在普通的1對1、1對多和多對多關聯查詢的基礎上附加額外的約束條件。它和上面的常規查詢的不同在於,自定義查詢關聯的對象不會被級聯更新。這些額外的約束條件一般也是一些簡單條件,但是靈活運用還是可以極大的減少我們的業務類代碼。
下面的例子比較復雜,讀者請留神看了。首先,這裡主要包括三個實體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.Name的所有Groups;User.AvailableTeams代表系統中所有IsAvailable的Team;特別注意User.DivingGroups代表了User所在的,但是,又是潛水(意思是User在Group中的狀態對Group中的其他成員是不可見的)的Groups,這個DivingGroups的CustomQuery標注的特殊之處在於,它的查詢約束條件”{IsHidden} = 1”中的IsHidden是User和Group的關聯實體UserGroup中的屬性,所以,這個CustomQuery標注也包含了RelationType屬性指定了關聯實體是UserGroup。同時,我們也可以使用OrderBy屬性,指定默認的排序規則。
正文結束