例子

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


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

有帮助吗?

解决方案

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

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

您可以为所有不同的子类创建不同的表,其中所有表都包含基类字段。从性能角度来看这是可以的。但从维护的角度来看并非如此。每次你的基类改变时,所有的表都会改变。

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

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

其他提示

看看马丁·福勒的 企业应用架构模式:

  • 单表继承:

    当映射到关系数据库时,我们尝试最小化在处理多个表中的继承结构时可以快速安装的联接。单表继承将继承结构的所有类的所有字段映射到单个表中。

  • 类表继承:

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

  • 具体表继承:

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

如果用户、人员和特殊人员都具有相同的外键,那么我将拥有一个表。添加一个名为“类型”的列,该列仅限于“用户”、“人员”或“特殊人员”。然后根据 Type 的值对其他可选列进行约束。

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

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

考虑一个数据库 看法 相当于接口定义。而一张表就相当于一个类。

因此,在您的示例中,所有 3 人类都将实现 IPerson 接口。因此,您有 3 张表 - 一张表对应“用户”、“人员”和“特殊人员”。

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

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

这可能不是OP想问的,但我想我可以把它放在这里。

我最近在一个项目中遇到了一个数据库多态性的独特案例。我们有 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 的某些属性来选择行。这只是一个小小的性能损失。

处理关系数据库中的继承有三种基本策略,以及根据您的具体需求而定的许多更复杂/定制的替代方案。

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

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

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

这样做的主要原因是它在逻辑上变得更加一致,并且您最终不会得到许多对于该特定记录来说是 NULL 和无意义的字段。当您迭代设计过程时,此方法还可以更轻松地向子类型添加额外的字段。

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

所以我的设计会是这样的

人员(人员、姓名、地址、电话、...)

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

用户(personid REFERENCES PERSON(personid)、用户名、加密密码、额外字段...)

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

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

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

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

在我们公司,我们通过将所有字段组合在一张表中来处理多态性,最糟糕的是,无法强制执行引用完整性,并且模型非常难以理解。我肯定会建议反对这种方法。

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

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

这是一篇较旧的文章,但我想我会从概念、程序和性能的角度进行权衡。

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

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

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

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

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

就我个人而言,我会将所有这些不同的用户类别存储在一个表中。然后,您可以拥有一个存储“类型”值的字段,也可以通过填写哪些字段来暗示您正在与什么类型的人打交道。例如,如果 UserID 为 NULL,则该记录不是用户。

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

如果您决定采用该方法,LINQ-to-SQL 也支持第一种方法(他们称之为“Table Per Hierarchy”或“TPH”)。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top