本文以双鱼座同学的再说继承关系一文中提到的一组三元继承关联关系为基础。
首先,分别实现本人的ORM中的继承关系映射全解一文中提到的三种实体继承体系到关系数据库的映射方案实例。
接着,使用接口分离以上继承体系中的实体类中的相同概念,对该继承体系进行重构,并同样给出对重构后的继承体系的三种到关系数据库的映射方案实例。
全部实例代码基于NBear的接口式实体定义方式实现。
1. 背景
首先,给出双鱼座同学原文中的三元继承关联关系的等价接口定义。见下图1:
2. 对图1-1的ORM映射
2.1 单表继承体系
采用单表继承体系映射时,我们用一个AllInOne表包含所有实体的所有字段,并用一个FilterMark过滤标志,区分该条纪录的类型,注意只有所有的User,包括抽象的User和所有的UserGroup是可以查询的,其余接口仅仅用于概念抽象。
代码1
[Table(IsView = true)]
public interface PrivilegeOwner : IEntity
{
[PrimaryKey]
int Id { get; }
string Name { get; set; }
}
[Table("AllInOne", AdditionalWhere = "FilterMark <= 20", IsView = true)]
public interface User : PrivilegeOwner
{
}
[Table("AllInOne", AdditionalInsert = "FilterMark = 1", AdditionalWhere = "FilterMark = 1")]
public interface LocalUser : User
{
string LoginId { get; set; }
string Password { get; set; }
}
[Table("AllInOne", AdditionalInsert = "FilterMark = 2", AdditionalWhere = "FilterMark = 2")]
public interface AgentUser : User
{
string LoginId { get; set; }
}
[Table("AllInOne", AdditionalInsert = "FilterMark = 3", AdditionalWhere = "FilterMark = 3")]
public interface GhostUser : User
{
}
[Table("AllInOne", AdditionalInsert = "FilterMark = 21", AdditionalWhere = "FilterMark = 21")]
public interface UserGroup : PrivilegeOwner
{
string Comment { get; set; }
}
2.2 一实体一具体表
采用一实体一具体表思路进行映射时,要为每个继承层次中的具体的实体定义一个重复包含所有父类字段的表,并在保存数据时,同时,更新字表和所有的父表。表示抽象契约的接口,在每个实现类包含一份重复字段定义,不需为这样的接口定义独立的表。
代码2
[Table("PrivilegeOwner")]
public interface PrivilegeOwner : IEntity
{
[PrimaryKey]
int Id { get; }
string Name { get; set; }
}
[Table("User")]
public interface User : PrivilegeOwner
{
}
[Table("LocalUser")]
public interface LocalUser : User
{
string LoginId { get; set; }
string Password { get; set; }
}
[Table("AgentUser")]
public interface AgentUser : User
{
string LoginId { get; set; }
}
[Table("GhostUser")]
public interface GhostUser : User
{
}
[Table("UserGroup")]
public interface UserGroup : PrivilegeOwner
{
string Comment { get; set; }
}
2.3 一实体一扩展表
采用一实体一扩展表思路进行映射时,注意将表示真正的继承概念中的基类或子类分离为独立的表,而将表示抽象契约的接口,在每个实现类包含一份重复字段定义,不需为这样的接口定义独立的表。对于没有扩展任何基类的属性的子类,也至少需要包含一个Id字段和它的父类关联。注意每一个XXXExtedned实体对应了实体扩展表相对于父类扩展的字段,而不带Extended后缀的表则大多是代表逻辑实体的视图。
代码3
[Table("PrivilegeOwner")]
public interface PrivilegeOwnerExtended : IEntity
{
[PrimaryKey]
int Id { get; }
string Name { get; set; }
}
[Table("PrivilegeOwner", IsView=true)]
public interface PrivilegeOwner : PrivilegeOwnerExtended
{
}
[Table("User")]
public interface UserExtended : IEntity
{
[PrimaryKey]
int Id { get; }
}
[Table("select * from User inner join PrivilegeOwner on PrivilegeOwner.Id = User.Id", IsView=true)]
public interface User : UserExtended, PrivilegeOwnerExtended
{
}
[Table("LocalUser")]
public interface LocalUserExtended : IEntity
{
[PrimaryKey]
int Id { get; }
string LoginId { get; set; }
string Password { get; set; }
}
[Table("select * from LocalUser inner join User on User.Id = LocalUser.Id inner join PrivilegeOwner on PrivilegeOwner.Id = LocalUser.Id", IsView=true)]
public interface LocalUser : LocalUserExtended, User
{
[PrimaryKey]
new int Id { get; }
}
[Table("AgentUser")]
public interface AgentUserExtended : IEntity
{
[PrimaryKey]
int Id { get; }
string LoginId { get; set; }
}
[Table("select * from AgentUser inner join User on User.Id = AgentUser.Id inner join PrivilegeOwner on PrivilegeOwner.Id = AgentUser.Id", IsView = true)]
public interface AgentUser : AgentUserExtended, User
{
[PrimaryKey]
new int Id { get; }
}
[Table("GhostUser")]
public interface GhostUserExtended : IEntity
{
[PrimaryKey]
int Id { get; }
}
[Table("select * from GhostUser inner join User on User.Id = GhostUser.Id inner join PrivilegeOwner on PrivilegeOwner.Id = GhostUser.Id", IsView=true)]
public interface GhostUser : GhostUserExtended, User
{
[PrimaryKey]
new int Id { get; }
}
[Table("UserGroup")]
public interface UserGroupExtended : IEntity
{
[PrimaryKey]
int Id { get; }
string Comment { get; set; }
}
[Table("select * from UserGroup inner join PrivilegeOwner on PrivilegeOwner.Id = UserGroup.Id", IsView=true)]
public interface UserGroup : UserGroupExtended, PrivilegeOwner
{
[PrimaryKey]
new int Id { get; }
}
3. 重构继承体系
图1-1的继承体系定义客观地说是比较简洁的,我相信也能满足当前的应用需求。重构的目的不是要推翻或者批驳原来的设计,而是尝试使得模型的可扩展性更强。例如,图1-1所示的模型中LocalUser和AgentUser都有一个LoginId,表示他们是可以登录的,那么,完全可以将可登陆语义抽象出来;再如,现在User和UserGroup都继承自PrivilegeOwner对象,代表他们都能赋权限,但是,将PrivilegeOwner作为User和UserGroup的基类会对User和UserGroup将来的扩展带来很大的限制,例如,在这个模型基础上,不能定义一个“不能赋权限”的GuestUser类型,也不能为UserGroup类指定另一个更抽象的Group作为基类,等等。
为了解除这样的限制,我们就要对这个模型进行重构,重构的核心就是使用接口分离以上继承体系中的实体类中的相同概念。重构后的继承体系见下图2:
注意图中表示继承的箭头。
首先,我们将Id和Name属性抽象为一个IdentableEntity接口,它代表了,继承类拥有一个唯一Id和一个Name描述的契约。User和UserGroup都从IdentableEntity继承,遵守该契约。抽象出该契约的目的是,将来有新的,拥有同样Id标识语义的实体时,可以通过继承该契约减少重复定义。
接着,我们抽象出PrivilegeAssignable接口,包含一个PrivilegeOwnerId用以和权限分配表关联,为可赋权限的对象分配权限。这样做的好处还在于,当需要定义新的拥有“可赋权限”概念的实体时,可以直接继承该接口,避免重复定义。
再接着,我们抽象出Loginable和PasswordLoginable接口,分别代表一个可登录对象和一个需要密码才能登录的对象的抽象契约。PasswordLoginable本身也是继承自Loginable的。
最后,我们可以看到,现在User和UserGroup都从IdentableEntity和PrivilegeAssinable继承,遵守“可标识”和“可赋权限”这两个契约;GhostUser,LocalUser和AgentUser都继承自User,并且,LocalUser和AgentUser分别继承自Loginable和PasswordLoginable接口,遵守“需要密码的可登录”和“可登录”契约。
我们可以发现,新的继承体系模型,比原始模型大大增强了可扩展性。例如,如果我们要添加一个不可设置权限的GuestUser对象,只需让User类不继承PrivilegeAssignable而成为所有User的基类,而使用一个新的继承User并实现PrivilegeAssignable的PrivilegeAssignableUser类作为GhostUser、LocalUser和AgentUser的基类;再如,此时,如果要增加一个不能赋权限的Group抽象,我们也可以非常方便的增加一个Group抽象类作为UserGroup的基类,并将原来UserGroup到IdentableEntity的继承,改为Group到IdentableEntity的继承。
4. 对图2的ORM映射
4.1 单表继承体系
采用单表继承体系映射时,我们用一个AllInOne表包含所有实体的所有字段,并用一个FilterMark过滤标志,区分该条纪录的类型,注意只有所有的User,包括抽象的User和所有的UserGroup是可以查询的,其余接口仅仅用于概念抽象。
代码4
[Table(IsView=true)]
public interface IdentableEntity : IEntity
{
[PrimaryKey]
int Id { get; }
string Name { get; set; }
}
[Table(IsView=true)]
public interface Loginable : IEntity
{
string LoginId { get; set; }
}
[Table(IsView=true)]
public interface PasswordLoginable : Loginable
{
string Password { get; set; }
}
[Table(IsView = true)]
public interface PrivilegeAssignable
{
int PrivilegeOwnerId { get; set; }
}
[Table("AllInOne", AdditionalWhere = "FilterMark <= 20", IsView = true)]
public interface User : IdentableEntity, PrivilegeAssignable
{
}
[Table("AllInOne", AdditionalInsert = "FilterMark = 1", AdditionalWhere = "FilterMark = 1")]
public interface LocalUser : User, PasswordLoginable
{
}
[Table("AllInOne", AdditionalInsert="FilterMark = 2", AdditionalWhere="FilterMark = 2")]
public interface AgentUser : User, Loginable
{
}
[Table("AllInOne", AdditionalInsert = "FilterMark = 3", AdditionalWhere = "FilterMark = 3")]
public interface GhostUser : User
{
}
[Table("AllInOne", AdditionalInsert = "FilterMark = 21", AdditionalWhere = "FilterMark = 21")]
public interface UserGroup : IdentableEntity, PrivilegeAssignable
{
string Comment { get; set; }
}
4.2 一实体一具体表
采用一实体一具体表思路进行映射时,要为每个继承层次中的具体的实体定义一个重复包含所有父类字段的表,并在保存数据时,同时,更新字表和所有的父表。表示抽象契约的接口,在每个实现类包含一份重复字段定义,不需为这样的接口定义独立的表。
代码5
[Table(IsView=true)]
public interface IdentableEntity : IEntity
{
[PrimaryKey]
int Id { get; }
string Name { get; set; }
}
[Table(IsView=true)]
public interface Loginable : IEntity
{
string LoginId { get; set; }
}
[Table(IsView=true)]
public interface PasswordLoginable : Loginable
{
string Password { get; set; }
}
[Table(IsView = true)]
public interface PrivilegeAssignable
{
int PrivilegeOwnerId { get; set; }
}
[Table("User")]
public interface User : IdentableEntity, PrivilegeAssignable
{
}
[Table("LocalUser")]
public interface LocalUser : User, PasswordLoginable
{
}
[Table("AgentUser")]
public interface AgentUser : User, Loginable
{
}
[Table("GhostUser")]
public interface GhostUser : User
{
}
[Table("UserGroup")]
public interface UserGroup : IdentableEntity, PrivilegeAssignable
{
string Comment { get; set; }
}
4.3 一实体一扩展表
代码6
[Table(IsView=true)]
public interface IdentableEntity : IEntity
{
[PrimaryKey]
int Id { get; }
string Name { get; set; }
}
[Table(IsView=true)]
public interface Loginable : IEntity
{
string LoginId { get; set; }
}
[Table(IsView=true)]
public interface PasswordLoginable : Loginable
{
string Password { get; set; }
}
[Table(IsView = true)]
public interface PrivilegeAssignable
{
int PrivilegeOwnerId { get; set; }
}
[Table("User")]
public interface UserExtended : IdentableEntity, PrivilegeAssignable
{
[PrimaryKey]
new int Id { get; }
}
[Table("User", IsView=true)]
public interface User : UserExtended
{
}
[Table("LocalUser")]
public interface LocalUserExtended : PasswordLoginable
{
[PrimaryKey]
int Id { get; }
}
[Table("select * from LocalUser inner join User on LocalUser.Id = User.Id", IsView=true)]
public interface LocalUser : LocalUserExtended, User
{
[PrimaryKey]
new int Id { get; }
}
[Table("AgentUser")]
public interface AgentUserExtended : Loginable
{
[PrimaryKey]
int Id { get; }
}
[Table("select * from AgentUser inner join User on AgentUser.Id = User.Id", IsView = true)]
public interface AgentUser : AgentUserExtended, User
{
}
[Table("GhostUser")]
public interface GhostUserExtended : IEntity
{
[PrimaryKey]
int Id { get; }
}
[Table("select * from GhostUser inner join User on GhostUser.Id = User.Id", IsView = true)]
public interface GhostUser : GhostUserExtended, User
{
}
[Table("UserGroup")]
public interface UserGroupExtended : IdentableEntity, PrivilegeAssignable
{
[PrimaryKey]
new int Id { get; }
string Comment { get; set; }
}
[Table("UserGroup", IsView=true)]
public interface UserGroup : UserGroupExtended
{
}
5. 使用映射的实体对于以上各种方案定义的实体,我们都可以使用NBear.Data.Gateway和NBear.Data.ActiveEntity类进行查询和更新。详细信息请参考
NBear中文用户手册。
另外,
注意,所有代码示例中的select *在实际使用环境请用字段名列表代替,以避免可能的数据表字段名重名错误。