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

时间:2018-12-21 11:36:23

标签: python django inheritance design-patterns data-modeling

tl; dr

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

前提

请根据以下内容考虑下图中非常基本的数据模型模式: Hay, 1996

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

此处的重点AddressParty有明确的关系,而不是与各个子模型Organization和{ {1}}。

diagram showing basic data model

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

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

实施

我认为最明显的实现将使用multi-table-inheritance

Person

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

但是,多表继承似乎是frowned upon,特别是从性能的角度来看,尽管it depends on the application。尤其是Django的一位创建者发布的scary, but ancient帖子令人沮丧:

  

从长远来看,在几乎每种情况下,抽象继承都是一种更好的方法。我看到有不止几个站点在具体继承所带来的负担下被压垮,因此我强烈建议Django用户对任何具体继承的使用都抱有很大的怀疑态度。

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

  

这些联接往往是“隐藏的”(它们是自动创建的),并且意味着看起来好像简单的查询通常不是这样。

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

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

替代品

对于我来说,抽象继承似乎不是一个可行的选择,因为我们无法为抽象模型设置外键,这是有意义的,因为它没有表。我想这暗示着我们将需要为每个“子”模型添加一个外键,并需要一些额外的逻辑来进行模拟。

代理继承似乎也不是一种选择,因为每个子模型都引入了额外的字段。 编辑:再考虑一下,如果我们在数据库级别使用Single 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) PartyOrganization

GenericForeignKey关系可能是some specific cases中的一种选择,但对我来说,这是噩梦。

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

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

通过检查数据库中的结果表,很明显,在数据库级别上, mti eoto 方法之间的唯一区别在于, > eoto models.Model表中有一个JOIN列作为主键,在Person处有一个单独的外键列,而 mti { {1}}表具有 no 单独的id列,但使用Party.id的外键作为其主键。

问题

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

一个明确的一对一关系真的比多表继承要好得多,除了它迫使我们使查询更加明确之外。对我来说,多表方法的便利性和清晰度超过了明确性参数。

请注意this SO question非常相似,但并不能完全回答我的问题。而且,最新答案现在已经有将近9年了,而Django从那以后发生了很大变化。

[1]:Hay 1996, Data Model Patterns

1 个答案:

答案 0 :(得分:2)

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

Kevin Christopher Henry在以上注释中所建议,从数据库端解决问题是有意义的。由于我在数据库设计方面的经验有限,因此这部分我必须依靠其他人。

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

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

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

目前 上下文,我想重要的是要注意数据模型之间的直接转换, 面向对象实现(Django)和 relational 数据库实现并不总是 可能甚至是理想的。一个很好的三向维恩图可能可以说明这一点。

数据模型级别

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

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

关系数据库级别

关于(单个)继承的数据库实现的一些SO答案是:

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

  

对于任何继承结构,基本上都有三个选项。   您可以为该层次结构中的所有类使用一个表:单表继承(278)...;   每个具体类一个表:具体表继承(293)...;   或层次结构中每个类的一个表:类表继承(285)...

  

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

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

应用程序级别

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

Django Model inheritance docs 根据所使用的模型类的类型(concreteabstractproxy)区分三种“继承样式”:

  1. 抽象父母,有具体个孩子(abstract base classes): 父类具有 no 数据库表。而是每个子类都有自己的数据库 表具有其自己的字段和父字段的重复项。 这听起来很像数据库中的Concrete Table Inheritance

  2. 具体父级,带有具体个子级(multi-table inheritance): 父类具有一个带有自己字段的数据库表,每个子类 拥有自己的表,自己的字段和外键(作为主键) 父表。 看起来像数据库中的Class Table Inheritance

  3. 具体父级,带有 proxy 子级(proxy models): 父类具有数据库表,而子类没有。 而是,子类直接与父表进行交互。 现在,如果我们添加子级的所有字段(在数据模型中定义) 到父类,这可以解释为 Single Table Inheritance。 代理模型提供了一种便捷的方式来处理 单个大型数据库表。

结论

在我看来,对于当前示例,单表继承与Django的 proxy 模型的组合可能是一个不错的解决方案,它没有以下缺点“隐藏”加入。

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

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)