?本手册演示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对多(外键)关联中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;
}
}
??? 三、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的含义都同该注解。
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; }
}
五、1对多自关联
分析:1对多自关联和普通的1对多关联的定义方法完全相同。
四、1对多关联
分析:一对多关联指一个实体通过被关联实体的一个外键关联多个被关联实体的集合。举例来说,下面的图表示Team可以包含多个User的关联。
自己做主:个性T恤图案设计
设计自己的个性QQ动态表情
知道吗?我喜欢你:几米风格设计
自己动手设计封面
欧美经典金质徽标设计
在VB下设计开发实时的数据采集曲线
钻石戒指经典宣传设计
春雨下的浪漫—设计一把心爱的雨伞
VB程序界面设计经验点滴
艺术:国外经典插画设计
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;}
}
注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; }
}
五、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; }
}
注2:在实体Group和User中,我们必须为属性Users和Groups设置ManyToManyQueryAttribute,并且用关联实体UserGroup的类型值作为ManyToManyQuery的唯一的构造函数参数。
本文作者: #p#副标题#e#
七、自定义关联
分析:除了以上常规的关联关系之外,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;}
}