Django альтернативы наследования нескольких таблиц для базовой модели модели данных - PullRequest
0 голосов
/ 21 декабря 2018

tl; dr

Существует ли простая альтернатива многостоловому наследованию для реализации базового шаблона модели данных, изображенного ниже, в Django?

Предпосылка

Пожалуйстарассмотрим очень базовую модель модели данных на изображении ниже, основанную, например, на Hay, 1996 .

Проще говоря: Organizations и Persons равны Parties, и все Parties имеют Address es.Подобный образец может применяться ко многим другим ситуациям.

Важным моментом здесь является то, что Address имеет явное отношение с Party, а не явные отношения с отдельными подмоделями Organization и Person.

diagram showing basic data model

Обратите внимание, что каждая подмодель вводит дополнительные поля (здесь не показано, но см. Пример кода ниже).

Этот конкретный примеримеет несколько очевидных недостатков, но это не относится к делу.Ради этого обсуждения предположим, что шаблон прекрасно описывает то, чего мы хотим достичь, поэтому остается только вопрос: как реализовать шаблон в Django .

Реализация

Я полагаю, что наиболее очевидная реализация будет использовать multi-table-Наследование :

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 ссылается на «наследование нескольких таблиц» как «конкретное наследование», которое не следует путать с Наследование бетонных таблиц на уровне базы данных.Последнее на самом деле лучше соответствует понятию наследования в Django с использованием абстрактных базовых классов.

Я думаю, этот вопрос SO хорошо иллюстрирует проблему "скрытых объединений".

Альтернативы

Абстрактное наследование не кажется мне жизнеспособной альтернативой, потому что мы не можем установить внешний ключ для абстрактной модели, что имеет смысл, потому что у него нет таблицы.Я предполагаю, что это подразумевает, что нам понадобится внешний ключ для каждой «дочерней» модели плюс некоторая дополнительная логика для имитации этого.

Наследование прокси также не кажется опцией, так как каждая из подмоделей вводит дополнительные поля, EDIT: Если подумать, прокси-модели могут быть вариантом, если мы используем Single Table Inheritance на уровне базы данных, т.е. используем одну таблицу, которая включает в себя всеполя из Party, Organization и Person.

GenericForeignKey могут быть опцией в некоторых особых случаях , но для меня они - материалночных кошмаров.

В качестве другой альтернативы часто предлагается использовать явные взаимно-однозначные отношения (здесь кратко eoto ) вместо наследования нескольких таблиц (так что Party, Person и Organization будут просто подклассами models.Model).

Обаpproaches, наследование нескольких таблиц ( mti ) и явные отношения один к одному ( eoto ) приводят к трем таблицам базы данных.Таким образом, в зависимости от типа запроса, конечно, , некоторая форма JOIN часто неизбежна при получении данных.

Изучив результирующие таблицы в базе данных, становится ясно, что единственное различие между mti и eoto на уровне базы данных состоит в том, что eoto Person таблица имеет столбец id в качестве первичного ключа и отдельный столбец внешнего ключа для Party.id, тогда как таблица mti Person имеет нет отдельный id столбец, но вместо этого используется внешний ключ к Party.id в качестве первичного ключа.

Вопрос (ы)

Я не думаю, что поведениеиз примера (особенно одного прямого отношения к родителю) можно добиться с помощью абстрактного наследования , не так ли?Если это может , то как бы вы этого достигли?

Действительно ли явное отношение один к одному действительно намного лучше, чем наследование нескольких таблиц, за исключением того факта, что оно вызываетнам сделать наши запросы более явными?Для меня удобство и ясность многостолового подхода перевешивает аргумент явности.

Примечание , что этот вопрос SO очень похож, но не совсем отвечает на мойвопросы.Более того, последний ответ существует уже почти девять лет , и с тех пор Django сильно изменился.

[1]: Hay 1996, Шаблоны моделей данных

1 Ответ

0 голосов
/ 22 декабря 2018

В ожидании лучшего, вот моя попытка ответа.

Как подсказывает Кевин Кристофер Генри в комментариях выше, имеет смысл подойти к проблеме со стороны базы данных,Поскольку мой опыт проектирования баз данных ограничен, я должен полагаться на других в этой части.

Пожалуйста, исправьте меня, если я ошибаюсь в любой момент.

Модель данных против (Object-Ориентировано) Приложение против (реляционной) базы данных

Многое можно сказать об несоответствии объекта / реляции или, точнее, несоответствии модели данных / объекта / несоответствия реляции.

В данном контексте, я думаю, важно отметить, что прямой перевод между модель данных , объектно-ориентированная реализация (Django) и реляционная реализация базы данных не всегда возможна или даже желательна.Хорошая трехсторонняя диаграмма Венна, вероятно, могла бы проиллюстрировать это.

Уровень модели данных

Для меня модель данных, как показано в исходном посте, представляет собой попытку уловить сутьинформационная система реального мира.Он должен быть достаточно подробным и гибким, чтобы мы могли достичь нашей цели.Он не предписывает подробности реализации, но, тем не менее, может ограничивать наши параметры.

В этом случае наследование создает проблему в основном на уровне реализации базы данных.

Уровень реляционной базы данных

Некоторые ответы SO, касающиеся реализаций базы данных (одиночного) наследования:

Все они более или менее следуют шаблонам, описанным в книге Мартина Фаулера Шаблоны прикладной архитектуры .Пока не получится лучший ответ, я склонен доверять этим взглядам.Раздел наследования в главе 3 (издание 2011 г.) хорошо подытоживает:

Для любой структуры наследования в основном есть три варианта.Вы можете иметь одну таблицу для всех классов в иерархии: Наследование одной таблицы (278) ...;одна таблица для каждого конкретного класса: Наследование бетонного стола (293) ...;или одна таблица на класс в иерархии: Наследование таблиц классов (285) ...

и

Все компромиссы находятся междудублирование структуры данных и скорости доступа.... Здесь нет явного победителя.... Мой первый выбор, как правило, Наследование одной таблицы ...

Сводка шаблонов из книги находится на martinfowler.com .

Уровень приложения

Объектно-реляционное отображение (ORM) Django API позволяет нам реализовать эти три подхода, хотя сопоставление не является строго однозначным.

Django Документы по наследованию модели различают три "стиля наследования", основанные на типе используемого класса модели ( бетон , аннотация , прокси ):

  1. аннотация родитель с бетон дети ( абстрактные базовые классы ):родительский класс имеет нет таблицы базы данных.Вместо этого каждый дочерний класс имеет свою собственную таблицу базы данных со своими полями и дубликатами родительских полей.Это звучит очень похоже на Наследование бетонных таблиц в базе данных.

  2. бетон родитель с бетон дети ( наследование нескольких таблиц ): у родительского класса есть таблица базы данных со своими собственными полями, а у каждого дочернего класса есть своя собственная таблица со своими полями и внешним ключом (в качестве первичного ключа) для родительской таблицы.Это выглядит как Наследование таблиц классов в базе данных.

  3. бетон родитель с прокси дочерние элементы ( модели прокси ): у родительского класса есть таблица базы данных, но дочерние элементы не имеют.Вместо этого дочерние классы взаимодействуют напрямую с родительской таблицей.Теперь, , если мы добавим все поля из дочерних элементов (как определено в нашей модели данных) в родительский класс , это можно интерпретировать как реализацию Наследование отдельных таблиц .Прокси-модели предоставляют удобный способ работы со стороной приложения одной большой таблицы базы данных.

Заключение

Мне кажется, что для данного примераКомбинация 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...