代码之家  ›  专栏  ›  技术社区  ›  djvg Carl Meyer

基本数据模型模式的django多表继承方案

  •  4
  • djvg Carl Meyer  · 技术社区  · 7 年前

    TL;博士

    对于在django中实现下面描述的基本数据模型模式,有没有一个简单的替代多表继承的方法?

    前提

    请考虑下图中非常基本的数据模型模式,例如: Hay, 1996 .

    简单地说: Organizations Persons Parties ,以及所有 当事人 Address 锿。类似的模式可能适用于许多其他情况。

    这里的重点是 是那个 地址 Party ,而不是与单个子模型的显式关系 Organization Person 是的。

    diagram showing basic data model

    注意,每个子模型都引入了额外的字段(这里没有描述,但请参见下面的代码示例)。

    这个具体的例子有几个明显的缺点,但那不是重点。为了便于讨论,假设模式完美地描述了我们希望实现的目标,那么剩下的唯一问题是 如何在django中实现该模式 是的。

    实施

    我相信,最明显的实现 multi-table-inheritance 以下内容:

    class Party(models.Model):
        """ Note this is a concrete model, not an abstract one. """
        name = models.CharField(max_length=20)
    
    
    class Organization(Party):
        """ 
        Note that a one-to-one relation 'party_ptr' is automatically added, 
        and this is used as the primary key (the actual table has no 'id' 
        column). The same holds for Person.
        """
        type = models.CharField(max_length=20)
    
    
    class Person(Party):
        favorite_color = models.CharField(max_length=20)
    
    
    class Address(models.Model):
        """ 
        Note that, because Party is a concrete model, rather than an abstract
        one, we can reference it directly in a foreign key.
    
        Since the Person and Organization models have one-to-one relations 
        with Party which act as primary key, we can conveniently create 
        Address objects setting either party=party_instance,
        party=organization_instance, or party=person_instance.
    
        """
        party = models.ForeignKey(to=Party, on_delete=models.CASCADE)
    

    这似乎完全符合模式。这几乎让我相信这就是多表继承最初的目的。

    但是,多表继承似乎是 frowned upon ,尤其是从性能的角度来看,尽管 it depends on the application . 尤其是这个 scary, but ancient ,来自Django的一位创作者的帖子非常令人沮丧:

    在几乎所有情况下,从长远来看,抽象继承都是一种更好的方法。我看到超过几个站点在具体继承带来的负载下崩溃,因此id强烈建议django用户对具体继承的任何使用持很大怀疑态度。

    尽管有这个可怕的警告,我想这篇文章的要点是关于多表继承的以下观察:

    这些连接往往是自动创建的“隐藏”的,这意味着看起来简单的查询通常不是这样的。

    消歧 :上面的帖子将django的“多表继承”称为“具体继承”,不应将其与 Concrete Table Inheritance 在数据库级别。后者实际上更符合django使用抽象基类继承的概念。

    我猜 this SO question 很好地说明了“隐藏连接”问题。

    选择

    抽象继承对我来说似乎不是一个可行的选择,因为我们不能为抽象模型设置外键,这是有意义的,因为它没有表。我想这意味着我们需要为每个“child”模型加上一些额外的逻辑来模拟这个过程。

    代理继承看起来也不是一个选项,因为每个子模型都引入了额外的字段。 编辑: 再想一想,代理模型 能够 如果我们使用 Single Table Inheritance 在数据库级别,即使用一个包含 派对 , 组织 是的。

    GenericForeignKey 关系可能是 some specific cases 但对我来说这是噩梦。

    作为另一种选择,通常建议使用显式的一对一关系( eoto公司 简而言之,这里)而不是多表继承(所以 聚会 我是说, 组织 都只是 models.Model )中。

    两种方法,多表继承( MTI公司 )以及明确的一对一关系( eoto公司 ),生成三个数据库表。所以, depending on the type of query, of course ,某种形式的 JOIN 在检索数据时经常是不可避免的。

    通过检查数据库中的结果表,可以清楚地看到 MTI公司 eoto公司 在数据库级别,方法是 eoto公司 桌子有一个 id 列作为主键,另一个外键列作为 Party.id ,而 MTI公司 表有 分离 身份证件 列,但使用外键 参与方.id 作为它的主键。

    问题

    我不认为例子中的行为(特别是与父母的单一直接关系)可以通过 抽象继承 ,可以吗如果它 可以 ,那你怎么做到?

    显式一对一关系是否真的比多表继承好得多,除了它迫使我们使查询更显式对我来说,多表方法的方便性和清晰性超过了显式论证。

    注意 那个 this SO question 是非常相似的,但不完全回答我的问题。而且,最新的答案是 九年 现在已经很老了,从那以后django改变了很多。

    [1]: Hay 1996, Data Model Patterns

    1 回复  |  直到 7 年前
        1
  •  3
  •   djvg Carl Meyer    7 年前

    在等待一个更好的答案时,这是我试图得到的答案。

    由建议 Kevin Christopher Henry 在上面的注释中,从数据库方面处理问题是有意义的。由于我在数据库设计方面的经验有限,我不得不依赖其他人来完成这一部分。

    如果我在任何时候错了,请纠正我。

    数据模型vs(面向对象)应用vs(关系)数据库

    关于 object/relational mismatch , 或者,更准确地说,数据模型/对象/关系不匹配。

    在现在 我想重要的是要注意 数据模型 我是说, 面向对象的 实现(Django),以及 关系型 数据库实现,并不总是 可能的,甚至是可取的一个好的三向维恩图也许能说明这一点。

    数据模型级别

    对我来说,一个数据模型,如最初的文章所示,代表着一个试图捕捉真实世界信息系统的本质。它应该足够详细和灵活,使我们能够达到我们的目标。它没有规定实施细节,但可能会限制我们的选择。

    在这种情况下,继承主要在数据库实现级别上构成了一个挑战。

    关系数据库级

    处理(单一)继承的数据库实现的一些so答案是:

    这些或多或少都遵循了马丁·福勒书中描述的模式。 Patterns of Application Architecture . 在更好的答案出现之前,我倾向于相信这些观点。 第3章(2011年版)中的继承部分总结得很好:

    对于任何继承结构,基本上都有三个选项。 对于层次结构中的所有类,可以有一个表: 单表继承 (278)。。。; 每种混凝土等级一张桌子: 具体表继承 (293)…… 或层次结构中的每个类一个表: 类表继承 (285)……

    在数据结构重复和访问速度之间进行权衡。... 这里没有明显的赢家。... 我的第一选择是 单表继承 ...

    这本书的模式摘要见 martinfowler.com 是的。

    应用程序级别

    django的对象关系映射(orm) API 允许我们实现这三种方法,尽管映射不是 严格来说是一对一。

    Django Model inheritance docs 根据使用的模型类的类型,区分三种“继承样式”( concrete 我是说, abstract 我是说, proxy )以下内容:

    1. 摘要 父级 混凝土 儿童( abstract base classes )以下内容: 父类具有 数据库表。相反,每个子类都有自己的数据库 具有自己字段和父字段副本的表。 听起来很像 Concrete Table Inheritance 在数据库中。

    2. 混凝土 父级 混凝土 儿童( multi-table inheritance )以下内容: 父类有一个带有自己字段的数据库表,每个子类 具有自己的表及其自己的字段和外键(作为主键) 父表。 这看起来像 Class Table Inheritance 在数据库中。

    3. 混凝土 父级 代理 儿童( proxy models )以下内容: 父类有一个数据库表,但是子类 不要 是的。 相反,子类直接与父表交互。 现在, 如果我们添加子项中的所有字段(在数据模型中定义) 到父类 ,这可以解释为 Single Table Inheritance 是的。 代理模型为处理 单个大型数据库表。

    结论

    在我看来,在本例中 单表继承 与Django的 代理 模型可能是一个很好的解决方案,它没有“隐藏”连接的缺点。

    应用于原始帖子中的示例,它看起来像这样:

    class Party(models.Model):
        """ All the fields from the hierarchy are on this class """
        name = models.CharField(max_length=20)
        type = models.CharField(max_length=20)
        favorite_color = models.CharField(max_length=20)
    
    
    class Organization(Party):
        class Meta:
            """ A proxy has no database table (it uses the parent's table) """
            proxy = True
    
        def __str__(self):
            """ We can do subclass-specific stuff on the proxies """
            return '{} is a {}'.format(self.name, self.type)
    
    
    class Person(Party):
        class Meta:
            proxy = True
    
        def __str__(self):
            return '{} likes {}'.format(self.name, self.favorite_color)
    
    
    class Address(models.Model):
        """ 
        As required, we can link to Party, but we can set the field using
        either party=person_instance, party=organization_instance, 
        or party=party_instance
        """
        party = models.ForeignKey(to=Party, on_delete=models.CASCADE)