Django multi-table 基本数据模型模式的继承替代方案

Django multi-table inheritance alternatives for basic data model pattern

tl;博士

在 Django 中,是否有一种简单的替代多table 继承来实现下面描述的基本数据模型模式?

前提

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

简单的说:OrganizationsPersons都是Parties,而Parties都有Address。类似的模式可能适用于许多其他情况。

这里的重点AddressParty 有显式关系,而不是与各个子模型有显式关系 OrganizationPerson.

请注意,每个子模型都引入了额外的字段(此处未描述,但请参阅下面的代码示例)。

这个具体的例子有几个明显的缺点,但这不是重点。为了本次讨论,假设该模式完美地描述了我们希望实现的目标,那么剩下的唯一问题就是 如何在 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)

这似乎与模式完美匹配。这几乎让我相信这就是 multi-table-inheritance 最初的目的。

然而,多 table 继承似乎是 frowned upon, especially from a performance point-of-view, although . Especially this scary, but ancient,来自 Django 的一位创建者的 post 非常令人沮丧:

In nearly every case, abstract inheritance is a better approach for the long term. I’ve seen more than few sites crushed under the load introduced by concrete inheritance, so I’d strongly suggest that Django users approach any use of concrete inheritance with a large dose of skepticism.

尽管有这个可怕的警告,但我想 post 的要点是以下关于多 table 继承的观察:

These joins tend to be "hidden" — they’re created automatically — and mean that what look like simple queries often aren’t.

消歧义:上面的post指的是Django的"multi-table inheritance"为"concrete inheritance",不要和Concrete Table Inheritance混淆在数据库级别。后者实际上更符合 Django 的继承概念,使用抽象基 类.

我想 this SO question 很好地说明了 "hidden joins" 问题。

备选方案

抽象继承对我来说似乎不是一个可行的替代方案,因为我们不能为抽象模型设置外键,这是有道理的,因为它没有 table。我想这意味着我们需要为每个 "child" 模型加上一些额外的逻辑来模拟它。

代理继承似乎也不是一个选项,因为每个子模型都引入了额外的字段。 编辑: 转念一想,如果我们在数据库级别使用 Single Table Inheritance,代理模型 可能 是一个选项,即使用单个 table,包括 PartyOrganizationPerson.

中的所有字段

GenericForeignKey relations may be an option in some specific cases,但对我来说它们是噩梦。

作为另一种选择,通常建议使用显式一对一关系(eoto,这里简称为)而不是多table-继承(所以 PartyPersonOrganization 都只是 models.Model 的子 类。

两种方法,多table-继承(mti)和显式一对一关系(eoto ), 导致三个数据库 tables。因此,depending on the type of query, of course,某种形式的 JOIN 在检索数据时通常是不可避免的 table。

通过检查数据库中的结果 tables,很明显 mtieoto[=122= 之间的唯一区别] 方法,在数据库级别,是 eoto Person table 有一个 id 列作为主键,和一个单独的外键Party.id 的关键列,而 mti Person table 有 no 单独的 id列,而是使用 Party.id 的外键作为其主键。

问题

我不认为示例中的行为(尤其是与父级的单一直接关系)可以通过抽象继承实现,可以吗?如果可以,那你会如何实现?

显式一对一关系真的比多 table 继承好得多吗,除了它迫使我们使查询更显式之外?对我来说,多 table 方法的便利性和清晰性胜过明确性论点。

注意 this SO question 非常相似,但没有完全回答我的问题。此外,那里的最新答案现在差不多 9 年 了,Django 自那以后发生了很大变化。

[1]: Hay 1996, Data Model Patterns

在等待更好的答案时,这是我尝试的答案。

正如上面评论中 Kevin Christopher Henry 所建议的,从数据库端解决问题是有意义的。由于我在数据库设计方面的经验有限,这部分我不得不依赖其他人。

如果我有任何错误,请纠正我。

Data-model 与(Object-Oriented)应用程序与(关系)数据库[​​=81=]

关于 可以说很多, 或者,更准确地说,data-model/object/relational 不匹配。

在现在 context 我想重要的是要注意 data-model 之间的直接翻译, object-oriented 实现 (Django) 和 relational 数据库实现,并不总是 可能甚至是可取的。一个不错的 three-way Venn-diagram 可能可以说明这一点。

Data-model等级

对我来说,data-model 如原文 post 中所示,代表了试图捕捉现实世界信息系统本质的尝试。它应该足够详细和灵活,使我们能够实现我们的目标。它没有规定实施细节,但可能会限制我们的选择。

在这种情况下,继承主要在数据库实现层面提出了挑战。

关系数据库级别

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

  • How can you represent inheritance in a database?

  • How do you effectively model inheritance in a database?

  • Techniques for database inheritance?

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

For any inheritance structure there are basically three options. You can have one table for all the classes in the hierarchy: Single Table Inheritance (278) ...; one table for each concrete class: Concrete Table Inheritance (293) ...; or one table per class in the hierarchy: Class Table Inheritance (285) ...

The trade-offs are all between duplication of data structure and speed of access. ... There's no clearcut winner here. ... My first choice tends to be Single Table Inheritance ...

可在 martinfowler.com 上找到书中的模式摘要。

应用级别

Django 的 object-relational 映射 (ORM) API 允许我们实现这三种方法,尽管映射不是 严格 one-to-one.

Django Model inheritance docs 根据使用的模型类型 class (concrete, abstract, proxy):

区分三种“继承样式”
  1. 抽象parent与具体child仁(abstract base classes ): parent class 有 没有 数据库 table。相反,每个 child class 都有自己的数据库 table 有自己的字段和 parent 字段的副本。 这听起来很像数据库中的Concrete Table Inheritance

  2. 具体 parent 和 具体 children (multi-table inheritance ): parent class 有一个带有自己字段的数据库 table,每个 child class 有自己的 table 和自己的字段和 foreign-key(如 primary-key) parenttable。 这在数据库中看起来像 Class Table Inheritance

  3. concrete parent with proxy children (proxy models ): parentclass有数据库table,但是child人没有。 相反,child classes 直接与 parent table 交互。 现在,如果我们添加 children 中的所有字段(如我们的 data-model 中所定义) 对于 parent class,这可以解释为 Single Table Inheritance。 代理模型提供了一种处理应用程序端的便捷方式 单个大型数据库 table.

结论

在我看来,对于当前示例,单一 Table 继承 与 Django 的 代理 模型的组合可能是一个很好的解决方案,它没有“隐藏”连接的缺点。

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

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)

一个警告,来自Django proxy-model documentation

There is no way to have Django return, say, a MyPerson object whenever you query for Person objects. A queryset for Person objects will return those types of objects.

提出了一个潜在的解决方法 here