你如何处理数据库中的多态?

时间:2008-09-05 12:16:13

标签: database-design oop

实施例

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


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

13 个答案:

答案 0 :(得分:46)

看看Martin Fowler的 Patterns of Enterprise Application Architecture

  • Single Table Inheritance

      

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

  • Class Table Inheritance

      

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

  • Concrete Table Inheritance

      

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

答案 1 :(得分:41)

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

您可以使用所有对象的所有字段创建一个大表,并为该类型指定一个特殊字段。这很快但浪费空间,尽管现代数据库通过不存储空字段来节省空间。如果您只是在寻找表中的所有用户,那么每个类型的人都会变得很慢。并非所有or-mappers都支持此功能。

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

您也可以像建议的那样为每个班级创建一个表格。这样,您需要连接才能获取所有数据。所以性能较差。我认为这是最干净的解决方案。

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

答案 2 :(得分:6)

如果用户,人和特殊人都拥有相同的外键,那么我会有一个表。添加一个名为Type的列,该列被限制为User,Person或Special Person。然后基于Type的值对其他可选列有约束。

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

答案 3 :(得分:5)

我在这里要说的是将数据库架构师发送到conniptions中,但这里是:

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

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

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

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

答案 4 :(得分: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的某些属性来选择行。这只是一个很小的性能损失。

答案 5 :(得分:4)

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

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

这些appoaches中的每一个都提出了自己关于规范化,数据访问代码和数据存储的问题,尽管我个人的优先考虑是使用 table per subclass ,除非有特定的性能或结构原因。其中一个替代方案。

答案 6 :(得分:4)

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

这样做的主要原因是它在逻辑上变得更加一致,并且你不会为那个特定记录留下很多NULL和无意义的字段。在迭代设计过程时,此方法还可以更轻松地向子类型添加额外字段。

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

所以我的设计就像是

PERSON(人物,姓名,地址,电话......)

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

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

如果有必要,您也可以稍后创建VIEW,聚合超类型和子类型。

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

答案 7 :(得分:3)

我会说,根据Person和Special Person的区别,你可能不希望这个任务有多态性。

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

答案 8 :(得分:2)

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

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

答案 9 :(得分:1)

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

答案 10 :(得分:1)

这是一篇较老的帖子,但我认为我会从概念,程序和表现的角度来衡量。

我要问的第一个问题是人,专家和用户之间的关系,以及某人是否可能同时一个一个专家和一个用户。或者,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方式调用,但在结构上与只有"任何人"在主表中。并且,LEFT JOIN可用于可能需要考虑"注册用户"的查询。

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

答案 11 :(得分:0)

过去我完全按照你的建议完成了 - 有一个常用的Person表,然后是派生类的SpecialPerson。但是,我正在重新思考,因为Linq2Sql希望在同一个表中有一个字段表明区别。我没有太多地看过实体模型 - 非常确定允许其他方法。

答案 12 :(得分:-1)

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

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

如果你决定沿着那条路走下去,那么LINQ-to-SQL也支持第一种方法(他们称之为'每个层次表'或'TPH')。