在数据库中建模继承的最佳做法是什么?
有什么权衡(例如可疑性)?
(我对SQL Server和.NET最感兴趣,但我也想了解其他平台如何解决这个问题。)
答案 0 :(得分:136)
有几种方法可以在数据库中建模继承。您选择哪个取决于您的需求。以下是一些选项:
每种类型的表格(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
对于任何rowtype 0(Person)的行,startdate将始终为null。
每混凝土表格(TPC)
每个类都有自己完全形成的表,没有任何其他表的引用。
鉴于上述类,您最终得到了这些表:
table Person
------------
int id (PK)
string firstname
string lastname
table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
答案 1 :(得分:115)
正确的数据库设计与正确的对象设计完全不同。
如果您打算将数据库用于除了简单序列化对象之外的任何事情(例如报告,查询,多应用程序使用,商业智能等),那么我不建议从对象进行任何类型的简单映射到桌子。
许多人认为数据库表中的一行是一个实体(我花了很多年思考这些术语),但是一行不是一个实体。这是一个命题。数据库关系(即表格)表示关于世界的一些事实陈述。行的存在表明事实是真的(相反,它的缺席表明事实是错误的。)
通过这种理解,您可以看到面向对象程序中的单个类型可以存储在十几个不同的关系中。各种类型(通过继承,关联,聚合或完全无关联)可以部分存储在一个关系中。
最好问自己,你想要存储什么事实,你想要答案的问题是什么,你想要产生什么样的报告。
一旦创建了正确的数据库设计,创建查询/视图就很容易,您可以将对象序列化为这些关系。
示例:
在酒店预订系统中,您可能需要存储Jane Doe预订4月10日至12日Seaview Inn酒店的客房。这是客户实体的属性吗?它是酒店实体的属性吗?它是一个包含客户和酒店属性的预订实体吗?它可以是面向对象系统中的任何或所有这些东西。在数据库中,它不是那些东西。这只是一个事实。
要查看差异,请考虑以下两个查询。 (1)Jane Doe明年有多少酒店预订? (2)4月10日在Seaview Inn预订了多少间客房?
在面向对象的系统中,query(1)是客户实体的属性,query(2)是酒店实体的属性。这些是在API中公开这些属性的对象。 (尽管显然,获取这些值的内部机制可能涉及对其他对象的引用。)
在关系数据库系统中,两个查询都会检查保留关系以获取其数字,从概念上讲,没有必要打扰任何其他“实体”。
因此,它试图存储关于世界的事实 - 而不是试图存储具有属性的实体 - 构建适当的关系数据库。一旦设计得当,那么在设计阶段就可以轻松构建有用的查询,因为完成这些查询所需的所有事实都在适当的位置。
答案 2 :(得分:8)
简短的回答:你没有。
如果你需要序列化你的对象,使用ORM,甚至更好的东西,如activerecord或prevaylence。
如果您需要存储数据,请以关系方式存储(注意存储的内容,并注意Jeffrey L Whitledge刚才所说的内容),而不是受到对象设计的影响。
答案 3 :(得分:7)
从基类继承的子类可以看作是数据库中基类定义的弱实体,这意味着它们依赖于它们的基类,没有它就不能存在。我已经看过很多次,为每个子表存储唯一的ID,同时将FK保存到父表。一个FK就足够了,对于子表和基表之间的FK关系,使用on-delete级联启用会更好。
在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;
答案 4 :(得分:4)
您可以在数据库中设置两种主要的继承类型,每个实体的表和每个层次结构的表。
每个实体的表是您拥有一个具有所有子类的共享属性的基本实体表的位置。然后,每个子类具有另一个表,每个表只有适用于该类的属性。它们以PK的方式1:1联系在一起
每个层次结构的表是所有类共享一个表的地方,可选属性是可空的。它们也是一个鉴别字段,它是一个表示记录当前所持类型的数字
SessionTypeID是鉴别器
每个层次结构的目标查询速度更快,因为您不需要连接(仅限于鉴别器值),而每个实体的目标需要执行复杂连接,以便检测某些类型以及撤消其所有数据。
编辑:我在这里展示的图像是我正在进行的项目的屏幕截图。资产图像不完整,因此它的空白,但它主要是为了显示它的设置,而不是放在你的表中的内容。那取决于你 ;)。会话表包含虚拟协作会话信息,可以是多种类型的会话,具体取决于所涉及的协作类型。
答案 5 :(得分:1)
您将对数据库进行规范化,这实际上会反映您的继承。 它可能会降低性能,但这就是规范化的方式。你可能必须使用良好的常识才能找到平衡点。
答案 6 :(得分:1)
repeat of similar thread answer
在O-R映射中,继承映射到父表,其中父表和子表使用相同的标识符
例如
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表来列出表之间的继承关系,并指定实现每个表的行为的程序集和类名
看起来有点矫枉过正,但这完全取决于你想用它做什么!
答案 7 :(得分:1)
使用SQL ALchemy(Python ORM),您可以执行两种类型的继承。
我曾经历过的经历是使用单个表,并具有判别列。例如,绵羊数据库(不是开玩笑!)将所有绵羊存储在一个表中,而公羊和母羊则使用该表中的性别列进行处理。
因此,你可以查询所有的绵羊,并获得所有的羊。或者你只能通过Ram查询,它只会得到公羊。你也可以做一些关系只能是拉姆(即绵羊的陛下)等等。
答案 8 :(得分:1)
请注意,某些数据库引擎已经提供了本地继承机制,如Postgres。查看documentation。
例如,您将查询上面响应中描述的Person / Employee系统,如下所示:
/* This shows the first name of all persons or employees */ SELECT firstname FROM Person ; /* This shows the start date of all employees only */ SELECT startdate FROM Employee ;
这是您数据库的选择,您不需要特别聪明!