繁体   English   中英

基本数据模型模式的Django多表继承替代方案

[英]Django multi-table inheritance alternatives for basic data model pattern

tl;博士

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

前提

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

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

这里的重点AddressParty有明确的关系,而不是与各个子模型OrganizationPerson的明确关系。

显示基本数据模型的图表

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

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

执行

我相信最明显的实现是使用多表继承

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)

这似乎与模式完美匹配。 这几乎让我相信这就是多表继承最初的目的。

然而,多表继承似乎不受欢迎,尤其是从性能的角度来看,尽管它取决于应用程序 尤其是这篇来自 Django 的创建者之一的可怕但古老的帖子非常令人沮丧:

几乎在所有情况下,从长远来看,抽象继承都是一种更好的方法。 我已经看到不少网站在具体继承引入的负载下崩溃,所以我强烈建议 Django 用户以大量怀疑的态度对待具体继承的任何使用。

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

这些连接往往是“隐藏的”——它们是自动创建的——这意味着看似简单的查询通常并非如此。

消歧义:上文将Django的“多表继承”称为“具体继承”,不要与数据库层面的Concrete Table Inheritance相混淆。 后者实际上更符合 Django 使用抽象基类的继承概念。

我想这个 SO 问题很好地说明了“隐藏连接”问题。

备择方案

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

代理继承似乎也不是一个选项,因为每个子模型都引入了额外的字段。 编辑:再三考虑,如果我们在数据库级别使用单表继承,代理模型可能是一个选项,即使用包含来自PartyOrganizationPerson的所有字段的单个表。

GenericForeignKey关系在某些特定情况下可能是一种选择,但对我来说它们是噩梦。

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

多表继承 ( mti ) 和显式一对一关系 ( eoto ) 这两种方法都会产生三个数据库表。 因此,根据查询的类型,当然,在检索数据时通常不可避免地会采用某种形式的JOIN

通过检查数据库中的结果表,很明显,在数据库级别, mtieoto方法之间的唯一区别是eoto Person表有一个id列作为主键,以及一个单独的外键列到Party.id ,而mti Person没有单独的id列,而是使用Party.id的外键作为其主键。

问题)

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

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

请注意这个 SO 问题非常相似,但并不能完全回答我的问题。 此外,最新的答案现在已经快九岁了,Django 自那以后发生了很大变化。

[1]: Hay 1996,数据模型模式

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

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

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

数据模型与(面向对象)应用程序与(关系)数据库

关于对象/关系不匹配,或者更准确地说,数据模型/对象/关系不匹配,可以说很多。

在目前的情况下,我想重要的是要注意数据模型面向对象的实现 (Django) 和关系数据库实现之间的直接转换并不总是可能的,甚至是不可取的。 一个漂亮的三向维恩图可能可以说明这一点。

数据模型级别

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

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

关系数据库级别

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

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

对于任何继承结构,基本上都有三个选项。 您可以为层次结构中的所有类创建一个表: Single Table Inheritance (278)...; 每个具体类一张表: Concrete Table Inheritance (293)...; 或层次结构中每个类一个表: Class Table Inheritance (285)...

权衡都是在数据结构的复制和访问速度之间进行的。 ... 这里没有明确的赢家。 ...我的首选往往是单表继承...

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

应用层

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

Django 模型继承文档根据使用的模型类的类型( concreteabstractproxy )区分三种“继承风格”:

  1. 具有具体子项的抽象父项( 抽象基类):父类没有数据库表。 相反,每个子类都有自己的数据库表,其中包含自己的字段和父字段的副本。 这听起来很像数据库中的具体表继承

  2. 具体父类和具体子类( 多表继承):父类有一个数据库表,有自己的字段,每个子类都有自己的表,有自己的字段和父类的外键(作为主键)桌子。 这看起来像数据库中的 类表继承

  3. 代理子类的具体父类( 代理模型):父类有数据库表,但子类没有 相反,子类直接与父表交互。 现在,如果我们将子类(如我们的数据模型中定义的)的所有字段添加到父类,这可以解释为单表继承的实现。 代理模型提供了一种处理单个大型数据库表的应用程序端的便捷方式。

结论

在我看来,对于当前示例,单表继承与 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)

一个警告,来自Django 代理模型文档

每当您查询Person对象时,都无法让 Django 返回一个MyPerson对象。 Person对象的查询集将返回这些类型的对象。

此处介绍了一种可能的解决方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM