在数据库中建模继承的最佳实践是什么?
有哪些权衡(例如可查询性)?
(我对SQL Server和.NET最感兴趣,但我也想了解其他平台如何解决此问题。)
在数据库中建模继承的最佳实践是什么?
有哪些权衡(例如可查询性)?
(我对SQL Server和.NET最感兴趣,但我也想了解其他平台如何解决此问题。)
Answers:
有几种方法可以对数据库中的继承进行建模。选择哪种取决于您的需求。以下是一些选择:
每种类型的表格(TPT)
每个类都有自己的表。基类中包含所有基类元素,并且从基类派生的每个类都有自己的表,并且主键也是基类表的外键;派生表的类仅包含不同的元素。
因此,例如:
class Person {
public int ID;
public string FirstName;
public string LastName;
}
class Employee : Person {
public DateTime StartDate;
}
将产生类似以下的表格:
table Person
------------
int id (PK)
string firstname
string lastname
table Employee
--------------
int id (PK, FK)
datetime startdate
逐层表格(TPH)
有一个表代表所有继承层次结构,这意味着几列可能很稀疏。添加了一个区分符列,该列告诉系统这是什么类型的行。
给定上面的类,您最终得到此表:
table Person
------------
int id (PK)
int rowtype (0 = "Person", 1 = "Employee")
string firstname
string lastname
datetime startdate
对于行类型0(人员)的任何行,开始日期将始终为null。
逐表(TPC)
每个类都有自己完整的表,没有对其他表的引用。
给定上面的类,您最终得到这些表:
table Person
------------
int id (PK)
string firstname
string lastname
table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
正确的数据库设计与正确的对象设计完全不同。
如果您打算将数据库用于除简单地序列化对象(例如报表,查询,多应用程序使用,商业智能等)以外的其他用途,那么我建议您不要使用从对象到表的任何简单映射。
许多人认为数据库表中的一行是一个实体(我花了很多年思考这些术语),但是一行不是一个实体。这是一个命题。数据库关系(即表)表示有关世界的一些事实陈述。该行的存在指示事实为真(反之,该行的不存在指示事实为假)。
有了这样的理解,您可以看到面向对象程序中的单个类型可能存储在十几种不同的关系中。各种类型(通过继承,关联,聚合或完全不关联的类型)可以部分存储在单个关系中。
最好问自己,您要存储哪些事实,您要对哪些问题进行解答,您要生成哪些报告。
一旦创建了正确的数据库设计,创建查询/视图就很简单了,该查询/视图使您可以将对象序列化为这些关系。
例:
在酒店预订系统中,您可能需要存储Jane Doe在4月10日至12日在Seaview Inn预订房间的事实。那是客户实体的属性吗?它是酒店实体的属性吗?它是具有包括客户和酒店在内的属性的预订实体吗?在面向对象的系统中,可能是这些东西中的任何一个或全部。在数据库中,这些都不是。这仅仅是一个裸露的事实。
若要查看区别,请考虑以下两个查询。(1)Jane Doe明年有几家酒店预订?(2)4月10日,海景旅馆预订了多少房间?
在面向对象的系统中,查询(1)是客户实体的属性,而查询(2)是酒店实体的属性。这些对象将在其API中公开这些属性。(尽管很明显,获得这些值的内部机制可能涉及对其他对象的引用。)
在关系数据库系统中,两个查询都将检查保留关系以获取其编号,并且从概念上讲,无需打扰任何其他“实体”。
因此,通过尝试存储有关世界的事实(而不是尝试存储具有属性的实体),可以构建适当的关系数据库。一旦正确设计,就可以轻松构造在设计阶段未曾想到的有用查询,因为完成这些查询所需的所有事实都在其适当的位置。
Employment
表格,该表格收集所有就业及其开始日期。因此,如果知道a的当前雇用开始日期Employer
很重要,那么这可能是a的正确用例,该a View
通过查询包括该属性?(注意:似乎是因为我的昵称之后出现了“-”,我对您的评论没有任何通知)
简短的答案:你不知道。
如果您需要序列化对象,请使用ORM,甚至可以使用activerecord或prevaylence之类的更好的东西。
如果您需要存储数据,请以关系方式存储它(注意存储的内容,并注意Jeffrey L Whitledge所说的内容),而不是受对象设计影响的数据。
正如Brad Wilson所言,TPT,TPH和TPC模式是您的选择。但有几点注意事项:
从基类继承的子类可以看作是数据库中基类定义的弱实体,这意味着它们依赖于基类,没有基类就无法存在。我已经看到过很多次,每个子表都存储唯一的ID,同时还保留FK到父表。一个FK就足够了,它甚至更好,可以通过on-delete级联实现子表与基表之间的FK关系。
在TPT中,仅查看基表记录,就无法找到记录代表的子类。当您要加载所有记录的列表时(有时不必 select
在每个子表上执行此操作),有时需要这样做。一种解决方法是让一列代表子类的类型(类似于TPH中的rowType字段),因此以某种方式混合TPT和TPH。
假设我们要设计一个数据库,其中包含以下形状类图:
public class Shape {
int id;
Color color;
Thickness thickness;
//other fields
}
public class Rectangle : Shape {
Point topLeft;
Point bottomRight;
}
public class Circle : Shape {
Point center;
int radius;
}
上述类的数据库设计可以是这样的:
table Shape
-----------
int id; (PK)
int color;
int thichkness;
int rowType; (0 = Rectangle, 1 = Circle, 2 = ...)
table Rectangle
----------
int ShapeID; (FK on delete cascade)
int topLeftX;
int topLeftY;
int bottomRightX;
int bottomRightY;
table Circle
----------
int ShapeID; (FK on delete cascade)
int centerX;
int center;
int radius;
您可以在数据库中设置两种主要的继承类型,每个实体表和每个层次结构表。
每个实体的表是您拥有基础实体表的地方,该基础实体表具有所有子类的共享属性。然后,每个子类都有另一个表,每个表仅具有适用于该类的属性。他们的PK链接为1:1
每个层次结构的表是所有类共享一个表的位置,并且可选属性可以为空。它们也是鉴别符字段,该字段是一个数字,表示记录当前持有的类型
SessionTypeID是鉴别符
每个层次结构的目标都可以更快地查询,因为您不需要联接(只需区分值),而每个实体的目标则需要进行复杂的联接才能检测出某种类型的东西并检索其所有数据。
编辑:我在这里显示的图像是我正在处理的项目的屏幕截图。资产图片不完整,因此是空的,但主要是为了显示其设置方式,而不是放在表中的内容。那取决于你 ;)。会话表保存虚拟协作会话信息,并且可以是几种会话类型,具体取决于所涉及的协作类型。
在OR映射中,继承映射到父表,其中父表和子表使用相同的标识符
例如
create table Object (
Id int NOT NULL --primary key, auto-increment
Name varchar(32)
)
create table SubObject (
Id int NOT NULL --primary key and also foreign key to Object
Description varchar(32)
)
SubObject与Object具有外键关系。创建SubObject行时,必须首先创建一个Object行并在两行中使用Id
编辑:如果您还想对行为建模,则需要一个Type表,该表列出了表之间的继承关系,并指定了实现每个表的行为的程序集和类名
似乎有点杀伤力,但这全取决于您要使用它的目的!