58

例子

我有Person,SpecialPersonUser. Person他们SpecialPerson只是人——他们在网站上没有用户名或密码,但他们存储在数据库中以保存记录。用户拥有与在网站注册时相同的所有数据,Person并且可能SpecialPerson拥有用户名和密码。


你会如何解决这个问题?您是否有一个Person表来存储一个人的所有常见数据,并使用一个键来查找他们的数据SpecialPerson(如果他们是特殊的人)和 User(如果他们是用户),反之亦然?

4

13 回答 13

58

看看 Martin Fowler 的企业应用架构模式

  • 单表继承

    当映射到关系数据库时,我们会尽量减少在处理多个表中的继承结构时可能快速安装的连接。单表继承将继承结构的所有类的所有字段映射到一个表中。

  • 类表继承

    您需要清楚地映射到对象并允许在继承结构中的任何位置链接的数据库结构。类表继承通过在继承结构中为每个类使用一个数据库表来支持这一点。

  • 具体表继承

    从对象实例的角度考虑表,明智的做法是获取内存中的每个对象并将其映射到单个数据库行。这意味着具体表继承,其中继承层次结构中的每个具体类都有一个表。

于 2008-09-05T13:19:17.337 回答
46

将对象继承映射到数据库表通常有三种方式。

您可以制作一个大表,其中包含来自所有对象的所有字段以及该类型的特殊字段。这速度很快,但会浪费空间,尽管现代数据库通过不存储空字段来节省空间。而且,如果您只是在表中查找所有用户,那么对于其中的每种类型的人来说,事情都会变得很慢。并非所有的或映射器都支持这一点。

您可以使用包含基类字段的所有表为所有不同的子类创建不同的表。从性能的角度来看,这是可以的。但不是从维护的角度来看。每次您的基类更改时,所有表都会更改。

您也可以按照您的建议为每个班级制作一张桌子。这样,您需要连接才能获取所有数据。所以它的性能较低。我认为这是最干净的解决方案。

您想使用什么当然取决于您的情况。没有一个解决方案是完美的,所以你必须权衡利弊。

于 2008-09-05T12:26:59.693 回答
7

我在这里要说的是让数据库架构师陷入困境,但这里是:

将数据库视图视为接口定义的等价物。一个表相当于一个类。

因此,在您的示例中,所有 3 个人员类都将实现 IPerson 接口。所以你有 3 个表 - 'User'、'Person' 和 'SpecialPerson' 各一个。

然后有一个视图“PersonView”或任何从所有 3 个表中选择公共属性(由您的“界面”定义)到单个视图中的视图。在此视图中使用“PersonType”列来存储所存储人员的实际类型。

因此,当您运行可以对任何类型的人员进行操作的查询时,只需查询 PersonView 视图。

于 2008-09-05T12:29:20.093 回答
6

如果 User、Person 和 Special person 都具有相同的外键,那么我将有一个表。添加一个名为 Type 的列,该列被限制为 User、Person 或 Special Person。然后根据 Type 的值对其他可选列进行约束。

对于目标代码,如果您有单独的表或多个表来表示多态性,那并没有太大的区别。但是,如果您必须对数据库执行 SQL,那么如果在单个表中捕获多态性会容易得多……只要子类型的外键相同。

于 2008-09-05T12:25:15.533 回答
5

在关系数据库中处理继承有三种基本策略,以及一些更复杂/定制的替代方案,具体取决于您的确切需求。

  • 每个类层次结构的表。整个层次结构的一张表。
  • 每个子类的表。为每个子类创建一个单独的表,在子类表之间具有 0-1 关联。
  • 每个具体类的表。为每个具体类创建一个表。

这些方法中的每一个都提出了自己的关于规范化、数据访问代码和数据存储的问题,尽管我个人的偏好是使用每个子类的表,除非有特定的性能或结构原因来使用其中一种替代方案。

于 2008-09-05T12:38:34.160 回答
5

这可能不是 OP 想要问的,但我想我可能会把它扔在这里。

我最近在一个项目中遇到了一个独特的 db 多态性案例。我们有 60 到 120 个可能的类,每个类都有自己的一组 30 到 40 个唯一属性,以及所有类的大约 10 到 12 个公共属性。我们决定采用 SQL-XML 路线并最终得到一个表。就像是 :

PERSON (personid,persontype, name,address, phone, XMLOtherProperties)

包含所有常见属性作为列,然后是一个大的 XML 属性包。然后,ORM 层负责从 XMLOtherProperties 读取/写入相应的属性。有一点像 :

 public string StrangeProperty
{
get { return XMLPropertyBag["StrangeProperty"];}
set { XMLPropertyBag["StrangeProperty"]= value;}
}

(我们最终将 xml 列映射为 Hastable 而不是 XML 文档,但您可以使用最适合您的 DAL 的任何内容)

它不会赢得任何设计奖项,但如果你有大量(或未知)可能的类,它会起作用。在 SQL2005 中,您仍然可以在 SQL 查询中使用 XPATH 来根据存储为 XML 的某些属性选择行。这只是一个小的性能损失。

于 2008-09-05T13:12:19.553 回答
4

冒着成为“建筑宇航员”的风险,我更倾向于为子类使用单独的表格。让子类表的主键也是链接回超类型的外键。

这样做的主要原因是它在逻辑上变得更加一致,并且您最终不会得到很多针对该特定记录的 NULL 和无意义的字段。这种方法还使得在迭代设计过程时向子类型添加额外字段变得更加容易。

这确实增加了在查询中添加 JOIN 的缺点,这会影响性能,但我几乎总是先采用理想的设计,然后在证明有必要时再进行优化。有几次我先走“最佳”方式,但后来我几乎总是后悔。

所以我的设计会是这样的

PERSON (personid, name, address, phone, ...)

SPECIALPERSON (personid REFERENCES PERSON(personid), 额外字段...)

USER (personid REFERENCES PERSON(personid), username, encryptedpassword, extra fields...)

如果有必要,您还可以稍后创建聚合超类型和子类型的视图。

这种方法的一个缺陷是,如果您发现自己在大量搜索与特定超类型相关的子类型。在我的脑海中没有简单的答案,您可以在必要时以编程方式跟踪它,或者运行 soem 全局查询并缓存结果。这将取决于应用程序。

于 2008-09-05T12:41:46.913 回答
3

我想说的是,根据 Person 和 Special Person 的区别,您可能不希望这项任务使用多态性。

我会创建一个 User 表,一个 Person 表,它有一个 User 可以为空的外键字段(即,Person 可以是 User,但不是必须的)。
然后我会制作一个与 Person 表相关的 SpecialPerson 表,其中包含任何额外的字段。如果在 SpecialPerson 中存在给定 Person.ID 的记录,则他/她/它是一个特殊的人。

于 2008-09-05T12:26:51.473 回答
2

在我们公司,我们通过将所有字段组合在一个表中来处理多态性,并且可以强制执行最差且没有参照完整性并且非常难以理解的模型。我肯定会反对这种方法。

我会使用每个子类的表,也可以避免性能下降,但使用 ORM,我们可以通过基于类型动态构建查询来避免加入所有子类表。上述策略适用于单个记录级别的拉取,但对于批量更新或选择,您无法避免。

于 2012-07-17T23:06:54.370 回答
2

这是一篇较旧的帖子,但我想我会从概念、程序和性能的角度来权衡一下。

我要问的第一个问题是person、specialperson和user之间的关系,以及是否有可能同时成为specialperson和user。或者,4 种可能组合中的任何其他组合(a + b 类、b + c 类、a + c 类或 a + b + c 类)。如果此类存储为type字段中的值并因此折叠这些组合,并且这种折叠是不可接受的,那么我认为需要一个辅助表来允许一对多关系。我了解到,在您评估使用情况和丢失组合信息的成本之前,您不会判断这一点。

另一个让我倾向于一张桌子的因素是你对场景的描述。 User是唯一具有用户名(例如 varchar(30))和密码(例如 varchar(32))的实体。如果公共字段的可能长度平均为每 20 个字段 20 个字符,那么您的列大小增加是 62 超过 400,或大约 15% - 10 年前,这将比使用现代 RDBMS 系统更昂贵,尤其是使用可用的字段类型如 varchar(例如,对于 MySQL)。

而且,如果您关心安全性,那么拥有一个名为credentials ( user_id, username, password). 该表将在登录时根据上下文在 JOIN 中调用,但在结构上与主表中的“任何人”分开。并且,aLEFT JOIN可用于可能要考虑“注册用户”的查询。

多年来,我的主要考虑仍然是考虑对象在数据库之外和现实世界中的重要性(以及可能的演变)。在这种情况下,所有类型的人都有跳动的心(我希望如此),并且彼此之间也可能有等级关系;所以,在我的脑海里,即使不是现在,我们也可能需要用另一种方法来存储这样的关系。这与您的问题没有明确相关,但它是对象关系表达的另一个示例。到现在(7 年后)你应该对你的决定是如何运作的有很好的了解:)

于 2015-02-28T11:14:56.403 回答
1

是的,如果可能会有更多类型,我还会考虑 TypeID 和 PersonType 表。但是,如果只有 3 个不应该是 nec。

于 2008-09-05T12:23:59.310 回答
0

在过去,我完全按照您的建议完成了它——有一个用于常见东西的 Person 表,然后为派生类链接 SpecialPerson。但是,我正在重新考虑,因为 Linq2Sql 希望在同一个表中有一个字段来指示差异。不过,我并没有过多地研究实体模型——很确定它允许使用其他方法。

于 2008-09-05T12:21:56.553 回答
-1

就个人而言,我会将所有这些不同的用户类存储在一个表中。然后,您可以拥有一个存储“类型”值的字段,或者您可以通过填写哪些字段来暗示您正在处理的人员类型。例如,如果 UserID 为 NULL,则此记录不是用户。

您可以使用一对一或无连接类型链接到其他表,但随后在每个查询中您将添加额外的连接。

如果您决定采用该方法(他们称其为“按层次结构表”或“TPH”),则 LINQ-to-SQL 也支持第一种方法。

于 2008-09-05T12:20:39.443 回答