ORM中的继承关系映射全解——单表继承体系、一实体一具体表、一实体一扩展表、接口映射
本文涉及的内容包括:
1.单表继承体系
2.一实体一具体表
3.一实体一扩展表
4.接口实现映射vs基类继承映射
1.单表继承体系
所谓单表继承体系就是用一张数据库表存储整个继承体系中的所有实体的数据。单表继承体系适合那种继承体系中实体数目相对较少,总记录数相对较少,子类对父类的属性扩展也相对较少的情形。
单表继承体系优点是读/写继承体系中的每个实体的数据,都只需操作一张表,性能较好,并且,新增继承类,或扩展实体属性都只需要增减一张表的字段就可以了,易于维护;主要缺点是,因为所有的实体共享一张表,表中会有比较多的NULL字段值的数据,浪费了一些存储空间,同时,如果记录数过多,表就会更庞大,也会影响表的读写性能。
简单单表继承体系
让我们先看一个假象的例子:
[Table("AllInOneTable")]
public interface Parent : IEntity
{
[PrimaryKey]
int ID { get; }
string Name { get; set; }
}
[Table("AllInOneTable")]
public interface AnotherParent : IEntity
{
[PrimaryKey]
int ID { get; }
int Age { get; set; }
}
[Table("AllInOneTable")]
public interface Child : Parent, AnotherParent
{
[PrimaryKey]
new int ID { get; set; }
DateTime Birthday { get; set; }
}
我们可以看到,在上例中,我们定义了两个基实体Parent和AnotherParent,Child实体同时从两个基类继承。注意,代码中加粗的行,如果多个不同的基接口包含相同名称的属性,代码会编译失败,此时,需要像这样使用new关键字来避免编译失败。
这里,我们采用的是单表继承体系方式,注意每个实体都映射到AllInOneTable这个表,只不过对每个实体来说,只使用了AllInOneTable表的部分字段。
但是,以这样的简单方式定义单表继承时,因为从表中读数据时无法知道一行数据真正对应的是哪一个子类,所以,实际情况下,一般我们都要附加一些查询条件和字段默认值。
带附加条件的单表继承体系
采用单表继承体系方案时,继承体系中的不同子类不仅仅扩展父类的属性,肯定还会附带一些字段查询条件和默认值。这里会用到NBear.Common.TableAttribute的AdditionalWhere和AdditionalInsert属性,关于这两个属性的详细用法请参考表映射。
请看下面的代码:
public interface Message : IEntity
{
[PrimaryKey]
int ID { get; }
string Title { get; set; }
string Content { get; set; }
DateTime UpdateTime { get; set; }
}
[Table("Message", AdditionalInsert = "[MessageType] = 1", AdditionalWhere = "[MessageType] = 1")]
public interface CommonMessage : Message
{
int UserID { get; set; }
}
[Table("Message", AdditionalInsert = "[MessageType] = 2", AdditionalWhere = "[MessageType] = 2")]
public interface SpecialMessage : Message
{
int GroupID { get; set; }
}
这里我们实际要持久化的是两个实体CommonMessage和SpecialMessage,他们有一个共同的抽象父类Message,父类作为一个抽象类不会被直接使用。我们将整个继承体系存于Message数据表。包含CommonMessage和SpecialMessage的所有属性。但是,就像我们在上面的假象示例中所说的,如果直接查询Message表,返回CommonMessage对应的字段数据,那么连SpecialMessage插入的那些数据也会被返回,反之亦然,这显然是不符合要求的。
因此,我们需要定义附加的查询条件和插入默认值,即为Message表增加一个MessageType字段,该字段值为1的数据代表CommonMessage,值为2代表该行数据是SpecialMessage,如上面的代码中定义AdditionalWhere和AdditionalInsert后,查询CommonMessage或SpecialMessae时,就只会返回真正对应到他们的MessageType值的记录了;当插入数据时,为CommonMessage和SpecialMessage,框架也会自动为其设置必要的MessageType默认值。