代码之家  ›  专栏  ›  技术社区  ›  Thomas Owens

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

  •  45
  • Thomas Owens  · 技术社区  · 16 年前

    例子

    我有 Person , SpecialPerson User . 专门人员 只是人们——他们在一个网站上没有用户名或密码,但是他们存储在数据库中以备记录。用户拥有与 和潜在的 专门人员 以及在站点中注册的用户名和密码。


    你将如何解决这个问题?你要一个吗 一个表,它存储一个人的所有公共数据,并使用键在 专门人员 (如果他们是特殊的人)和用户(如果他们是用户),反之亦然?

    13 回复  |  直到 9 年前
        1
  •  39
  •   approxiblue Ben Jackson    9 年前

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

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

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

    你也可以按照你的建议为每个班级做一张桌子。通过这种方式,您需要连接来获取所有数据。所以它的性能较差。我认为这是最干净的解决方案。

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

        2
  •  44
  •   approxiblue Ben Jackson    9 年前

    看看马丁·福勒的 Patterns of Enterprise Application Architecture :

    • Single Table Inheritance :

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

    • Class Table Inheritance :

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

    • Concrete Table Inheritance :

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

        3
  •  5
  •   andreasw    16 年前

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

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

        4
  •  5
  •   HS.    16 年前

    我在这里要说的是将数据库架构师置于纵容之下,但这里是:

    考虑数据库 看法 作为接口定义的等价物。 表是类的等价物。

    因此在您的示例中,所有3个人类都将实现IPerson接口。 所以你有三张表,分别是“用户”、“个人”和“特殊人”。

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

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

        5
  •  5
  •   Radu094    16 年前

    这可能不是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列映射为一个仓促的而不是一个XML文档,但是您可以使用最适合您的DAL的任何内容)

    它不会赢得任何设计奖项,但如果你有大量(或未知)可能的课程,它将起作用。在sql2005中,您仍然可以在SQL查询中使用xpath根据存储为xml的某些属性选择行。这只是一个小小的表演惩罚。

        6
  •  4
  •   Ubiguchi    16 年前

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

    • 每个类层次结构的表。整个层次结构的一个表。
    • 每个子类的表。为每个子类创建一个单独的表,子类表之间的关联为0-1。
    • 每个混凝土等级的表。为每个具体类创建一个表。

    这些应用程序中的每一个都提出了自己关于规范化、数据访问代码和数据存储的问题,尽管我个人倾向于使用 每个子类一张表 除非有一个特定的性能或结构上的原因去选择其中一个。

        7
  •  4
  •   John Douthat    16 年前

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

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

    这确实增加了向查询中添加联接的缺点,这可能会影响性能,但我几乎总是先使用理想的设计,然后在有必要时,稍后再进行优化。几次我都是先走“最优”的路,但后来我几乎总是后悔。

    所以我的设计应该是

    人员(个人ID、姓名、地址、电话等)

    特殊人员(个人ID引用个人(个人ID),额外字段…)

    用户(personid引用person(personid)、用户名、加密密码、额外字段…)

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

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

        8
  •  3
  •   Lars Mæhlum    16 年前

    我会说,根据区分人和特殊人的不同,您可能不希望此任务使用多态性。

    我将创建一个用户表,一个对用户具有可以为空的外键字段的个人表(也就是说,这个人可以是一个用户,但不必是)。
    然后我会创建一个特殊的人员表,它与人员表相关,其中包含任何额外的字段。如果某个给定的person.id的记录以special person形式存在,则他/她/它是一个特殊的人。

        9
  •  2
  •   Kashyap Shah    12 年前

    在我们的公司中,我们通过将所有字段组合在一个表中来处理多态性及其最糟的情况,并且不能强制执行引用完整性,而且很难理解模型。我肯定会反对这种做法。

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

        10
  •  1
  •   Sara Chipps    16 年前

    是的,如果可能会有更多的类型,我也会考虑一个typeid和一个persontype表。但是,如果只有3个不应该是NEC。

        11
  •  1
  •   Oliver Williams    10 年前

    这是一篇老文章,但我想我会从概念、程序和性能的角度来考虑。

    我要问的第一个问题是人、特殊人和用户之间的关系,以及某人是否可能成为 二者都 一个特殊的人和一个用户同时。或者,4种可能的组合中的任意一种(A+B、B+C、A+C或A+B+C)。如果该类作为值存储在 type 字段,因此会折叠这些组合,而这种折叠是不可接受的,那么我认为需要一个二级表来允许一对多的关系。我知道,在评估使用情况和丢失组合信息的成本之前,您不会判断这一点。

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

    而且,如果您关心安全问题,那么有一个名为 credentials ( user_id, username, password) . 在登录时,这个表将在上下文连接中被调用,但在结构上只与主表中的“任何人”分离。和A LEFT JOIN 对于可能需要考虑“已注册用户”的查询可用。

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

        12
  •  0
  •   Danimal    16 年前

    在过去,我完全按照您的建议做了这件事——为常见的东西准备一个Person表,然后为派生类链接SpecialPerson。但是,我认为,因为linq2sql希望在同一个表中有一个字段来指示差异。不过,我并没有太多地关注实体模型——这肯定允许使用其他方法。

        13
  •  -1
  •   Chris Roberts    16 年前

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

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

    如果您决定沿着该路径走下去(它们称之为“每个层次结构的表”或“tph”),那么linq-to-sql也支持第一个方法。