你如何处理数据库中的多态性?
例
我有Person
, SpecialPerson
和User
。 Person
和SpecialPerson
只是人 – 他们在网站上没有用户名或密码,但是他们被存储在数据库中以备logging。 用户具有与Person
相同的数据以及潜在的SpecialPerson
,以及在网站上注册的用户名和密码。
你将如何解决这个问题? 你是否有一个Person
表,它存储了一个Person
共有的所有数据,并使用一个键在SpecialPerson
(如果他是一个特殊的人)和User(如果他们是一个用户)查找他们的数据,反之亦然?
通常有三种将对象inheritance映射到数据库表的方法。
你可以用一个特殊的字段来创build一个包含所有对象的所有字段的大表。 虽然现代数据库通过不存储空字段来节省空间,但这很快,但浪费空间。 如果你只是在桌子上寻找所有的用户,那么每一个人都会变得很慢。 并不是所有的或映射器都支持这一点。
您可以为所有包含基类字段的表的所有不同子类创build不同的表。 从性能angular度来看,这是可以的。 但不是从维护的angular度来看。 每当你的基类改变所有的表变化。
您也可以按照您的build议为每个class级制作一张桌子。 这样你需要连接来获取所有的数据。 所以它性能较差。 我认为这是最干净的解决scheme。
你想使用什么取决于你的情况当然。 没有一个解决scheme是完美的,所以你必须权衡利弊。
看看Martin Fowler的企业应用架构模式 :
-
单表inheritance :
当映射到关系数据库时,我们尽量减less在多个表中处理inheritance结构时可以快速安装的联接。 单表inheritance将inheritance结构的所有类的所有字段映射到一个表中。
-
类表inheritance :
你需要清楚地映射到对象的数据库结构,并允许inheritance结构中任何地方的链接。 类表inheritance通过在inheritance结构中为每个类使用一个数据库表来支持这一点。
-
混凝土表inheritance :
从对象实例的angular度思考表格,一条明智的路线是将每个对象放在内存中,并将其映射到单个数据库行。 这意味着混凝土表inheritance,其中有inheritance层次结构中每个具体类的表。
我要在这里说的是将数据库架构师发送到里面,但是这里是:
考虑将数据库视图等同于接口定义。 而一张桌子就相当于一个class级。
所以在你的例子中,所有3个人类将实现IPerson接口。 所以你有3张桌子 – 每个'用户','人'和'SpecialPerson'。
然后有一个视图“PersonView”或任何从所有3个表中select共同属性(由“界面”定义)到单个视图。 在此视图中使用“PersonType”列来存储所存储人员的实际types。
所以,当你运行一个可以在任何types的人上进行操作的查询时,只需查询PersonView视图。
这可能不是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的最佳文档)
这不会赢得任何devise奖项,但是如果您有大量(或未知)可能的课程,它将会奏效。 而在SQL2005中,你仍然可以在你的SQL查询中使用XPATH来select基于存储为XML的属性的行。这只是一个小的性能损失。
如果用户,人员和特殊人员都有相同的外键,那么我会有一个表。 添加一个名为Type的列,该列被约束为User,Person或Special Person。 然后根据Type的值对其他可选列进行约束。
对于目标代码来说,如果您有单独的表或多个表来表示多态性,那么这并没有太大区别。 但是,如果您必须对数据库执行SQL,那么在单个表中捕获多态性会更容易…只要提供的子types的外键是相同的。
我会说,取决于人和特殊人的区别,你可能不希望这个任务的多态性。
我会创build一个用户表,一个Person表具有一个可为空的外键字段给用户(即人可以是用户,但不必)。
然后,我将创build一个与Person表相关的SpecialPerson表,其中包含任何额外的字段。 如果SpecialPerson中存在某个Person.ID的logging,他/她是一个特殊的人。
处理关系数据库中的inheritance有三个基本的策略,并根据您的确切需要提供一些更复杂/定制的替代scheme。
- 每个类层次的表。 一个整个层次的表
- 每个子类的表。 为每个子类创build一个单独的表,在子类表之间build立一个0-1关联。
- 表每具体类。 为每个具体的类创build一个表。
这些应用程序中的每一个都提出了关于规范化,数据访问代码和数据存储的自己的问题,虽然我个人的优先考虑是每个子类都使用表,除非有具体的性能或结构原因去select其中的一种。
在这里冒着“build筑宇航员”的风险,我会更倾向于为子类别单独列表。 让子类表的主键也是连接到超types的外键。
这样做的主要原因是,它变得更加逻辑一致,并且不会有很多对于特定logging是NULL和无意义的字段。 这个方法也使得迭代你的devise过程更容易在子类中添加额外的字段。
这确实会增加向查询添加JOIN的缺点,这可能会影响性能,但是我几乎总是先select一个理想的devise,然后再看看是否有必要进行优化。 有几次我首先走了“最佳”的路,后来我几乎总是后悔了。
所以我的devise会是这样的
人(personid,姓名,地址,电话,…)
SPECIALPERSON(personid参考人(personid),额外领域…)
USER(personid REFERENCES PERSON(personid),username,encryptedpassword,extra fields …)
如果需要的话,你也可以在以后创buildVIEW来聚合超types和子types。
这种方法的一个缺陷是如果你发现自己严重search与特定超types相关的子types。 对我来说,没有简单的答案,如果有必要,可以通过编程的方式进行跟踪,或者运行soem全局查询并caching结果。 这将取决于应用程序。
在我们公司,我们通过结合一个表中所有的字段和最差的字段来处理多态性,并且没有参照完整性可以执行,并且很难理解模型。 我肯定会推荐这种方法。
我会去每个子类,也避免性能影响,但使用ORM,我们可以避免通过build立基于types的查询飞行所有的子类表join。 上述策略适用于单个logging级别的提取,但是对于批量更新或select您无法避免它。
是的,如果可能会有更多的types,我也会考虑一个TypeID和一个PersonType表。 但是,如果只有3个不应该是必需的。
这是一个较旧的post,但我想我会从概念,程序和性能的angular度来衡量。
我要问的第一个问题是人,专人和用户之间的关系,以及是否有人同时成为专家和用户。 或者,4种可能的组合(a + b类,b + c类,a + c类或a + b + c类)中的任何一种。 如果这个类被作为一个值存储在一个type
字段中,并因此折叠这些组合,并且这个崩溃是不可接受的,那么我会认为需要一个辅助表来允许一对多的关系。 我了解到,在评估丢失组合信息的使用情况和成本之前,您不要判断。
另一个让我倾向于单一表格的因素是你对场景的描述。 User
是唯一使用用户名(例如varchar(30))和密码(比如varchar(32))的实体。 如果通用字段的可能长度是每20个字段平均20个字符,那么列大小就会增加到62个,超过400个,或者大约15% – 10年前,这个成本要比现在的RDBMS系统要高,尤其是可用的字段types如varchar(例如用于MySQL)。
而且,如果您担心安全问题,则可能有一个名为credentials ( user_id, username, password)
的辅助一对一表。 这个表将在login时在JOIN环境中被调用,但是在结构上与主表中的“任何人”分离。 而且,对于可能要考虑“注册用户”的查询,可以使用“ LEFT JOIN
”。
我多年来的主要考虑仍然是在数据库之外和现实世界中考虑对象的意义(因此也是可能的演变)。 在这种情况下,所有types的人都有心跳(我希望),也可能有层次关系; 所以,在我看来,即使不是现在,我们也可能需要用另一种方法来存储这种关系。 这里并没有明确提到你的问题,而是另一个expression对象关系的例子。 到现在(7年后),你应该对你的决定如何工作有很好的了解:)
就个人而言,我会将所有这些不同的用户类存储在一个表中。 然后你可以有一个存储'Type'值的字段,或者你可以通过填写什么字段来暗示你处理的是什么types的人。例如,如果UserID是NULL,那么这个logging不是用户。
你可以使用一对一或者无types的连接链接到其他表,但是在每个查询中你都会添加额外的连接。
如果您决定沿着这条路线走(他们称之为“按层次表”或“TPH”),第一种方法也被LINQ-to-SQL所支持。
在过去,我已经完全按照你的build议完成了 – 有一个普通的Person表,然后SpecialPerson链接派生类。 但是,我正在重新思考,因为Linq2Sql想要在同一个表中有一个字段表示不同。 我没有看太多的实体模型,虽然 – 非常确定,允许其他方法。