关于oop:您如何处理数据库中的多态性?

关于oop:您如何处理数据库中的多态性?

How do you deal with polymorphism in a database?

示例

我有PersonSpecialPersonUserPersonSpecialPerson只是人-他们在站点上没有用户名或密码,但是它们存储在数据库中以备记录。用户具有与Person和可能的SpecialPerson相同的所有数据,以及在站点中注册时的用户名和密码。

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


看看Martin Fowler的企业应用程序体系结构模式:

  • 单表继承:

    When mapping to a relational database, we try to minimize the joins that can quickly mount up when processing an inheritance structure in multiple tables. Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.

  • 类表继承:

    You want database structures that map clearly to the objects and allow links anywhere in the inheritance structure. Class Table Inheritance supports this by using one database table per class in the inheritance structure.

  • 混凝土表继承:

    Thinking of tables from an object instance point of view, a sensible route is to take each object in memory and map it to a single database row. This implies Concrete Table Inheritance, where there's a table for each concrete class in the inheritance hierarchy.


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

您可以创建一个大表,其中包含所有对象的所有字段以及类型的特殊字段。尽管现代数据库通过不存储空字段来节省空间,但速度很快,但是却浪费了空间。而且,如果您仅查找表中的所有用户,那么其中的每种类型的人都会变得很慢。并非所有or-mapper都支持此功能。

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

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

您要使用的内容当然取决于您的情况。没有一种解决方案是完美的,因此您必须权衡利弊。


在这里我要说的是使数据库架构师陷入困境,但实际上是这样:

将数据库视图视为等效于接口定义。
表等同于类。

因此,在您的示例中,所有3个人类都将实现IPerson接口。
因此,您有3个表-"用户","人"和"特殊人"中的每个表。

然后有一个" PersonView"视图或从所有3个表中选择通用属性(由"接口"定义的视图)的任何视图。
在此视图中使用" PersonType"列来存储要存储的人员的实际类型。

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


如果"用户","人"和"特殊人"都具有相同的外键,那么我将只有一个表。添加一列"类型",该列必须限制为"用户","人"或"特殊人"。然后基于Type的值在其他可选列上具有约束。

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


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

我最近在一个项目中有一个独特的db多态性案例。我们有60到120个可能的类,每个类都有自己的30到40个唯一属性集,所有类上都有大约10到12个共同属性。我们决定采用SQL-XML路由,最后只有一个表。像:

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

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

1
2
3
4
5
 public string StrangeProperty
{
get { return XMLPropertyBag["StrangeProperty"];}
set { XMLPropertyBag["StrangeProperty"]= value;}
}

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

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


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

以这种方式执行此操作的主要原因是,它在逻辑上变得更加一致,并且您最终不会获得很多对于该特定记录而言为NULL且毫无意义的字段。这种方法还使得在迭代设计过程时向子类型添加额外字段变得更加容易。

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

所以我的设计应该像

PERSON(人格,姓名,地址,电话等)

特殊人员(个人参考人(个人),其他字段...)

USER(personid参考人(personid),用户名,加密密码,其他字段...)

如果需要的话,您也可以稍后在其上创建聚合超类型和子类型的VIEW。

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


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

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

这些方法中的每一个都会引起有关规范化,数据访问代码和数据存储的问题,尽管我个人的喜好是每个子类使用表,除非有特定的性能或结构上的原因要使用其他选择之一。<铅>


我要说的是,根据"人"和"特殊人"的区别,您可能不希望该任务具有多态性。

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


这是一篇较旧的文章,但我认为我将从概念,过程和性能的angular进行讨论。

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

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

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

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


在我们公司中,我们通过将一个表中的所有字段组合在一起来处理多态性,并且最坏的情况是不能强制执行参照完整性,并且很难理解模型。我肯定会建议您反对这种方法。

我将使用每个子类的Table并避免性能下降,但是使用ORM,我们可以通过根据类型动态构建查询来避免与所有子类表联接。前面提到的策略适用于单个记录级别的提取,但对于批量更新或选择则无法避免。


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


过去,我完全按照您的建议进行了操作-为普通内容提供一个Person表,然后为派生类链接SpecialPerson。但是,我重新考虑一下,因为Linq2Sql希望在同一表中有一个字段来指示差异。不过,我对实体模型的关注并不多-可以肯定,这允许使用其他方法。


我个人将所有这些不同的用户类存储在一个表中。然后,您可以具有一个存储"类型"值的字段,或者可以通过填写哪些字段来隐含您正在处理的人的类型。例如,如果UserID为NULL,则此记录不是用户

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

如果您决定沿这条路线走,LINQ-to-SQL也支持第一种方法(他们称其为"每个层次的表"或" TPH")。


推荐阅读