在数据库中建模继承的最佳实践是什么?
权衡取舍(例如可疑性)是什么?
(我对SQL Server和.NET最感兴趣,但我也想了解其他平台如何解决这个问题.)
有几种方法可以在数据库中建模继承.您选择哪个取决于您的需求.以下是一些选项:
每表类型(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(Person)的行,startdate将始终为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日在Seaview Inn预订了多少间客房?
在面向对象的系统中,查询(1)是客户实体的属性,查询(2)是酒店实体的属性.这些是在API中公开这些属性的对象.(尽管显然,获取这些值的内部机制可能涉及对其他对象的引用.)
在关系数据库系统中,两个查询都会检查保留关系以获取其数字,并且在概念上不需要打扰任何其他"实体".
因此,它试图存储关于世界的事实 - 而不是试图存储具有属性的实体 - 构建适当的关系数据库.一旦设计得当,就可以轻松构建在设计阶段无意义的有用查询,因为完成这些查询所需的所有事实都在适当的位置.
简短的回答:你没有.
如果您需要序列化对象,请使用ORM,甚至更好的东西,如activerecord或prevaylence.
如果您需要存储数据,请以关系方式存储(注意存储的内容,并注意Jeffrey L Whitledge刚才所说的内容),而不是受到对象设计的影响.
如Brad Wilson所述,TPT,TPH和TPC模式是你的方式.但几个笔记:
从基类继承的子类可以看作是数据库中基类定义的弱实体,这意味着它们依赖于它们的基类,没有它就不能存在.我已经看过很多次,为每个子表存储唯一的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;