Как вы справляетесь с полиморфизмом в базе данных?

StackOverflow https://stackoverflow.com/questions/45621

  •  09-06-2019
  •  | 
  •  

Вопрос

Пример

У меня есть Person, SpecialPerson, и User. Person и SpecialPerson это просто люди - у них нет имени пользователя или пароля на сайте, но они хранятся в базе данных для ведения учета.Пользователь имеет все те же данные, что и Person и потенциально SpecialPerson, а также имя пользователя и пароль, поскольку они зарегистрированы на сайте.


Как бы вы решили эту проблему?Не могли бы вы иметь Person таблица, в которой хранятся все данные, общие для человека, и используется ключ для поиска их данных в SpecialPerson (если это особый человек) и Пользователь (если он пользователь) и наоборот?

Это было полезно?

Решение

Обычно существует три способа сопоставления наследования объектов с таблицами базы данных.

Можно сделать одну большую таблицу со всеми полями от всех объектов со специальным полем для типа.Это быстро, но требует лишнего места, хотя современные базы данных экономят место, не сохраняя пустые поля.А если вы ищете только всех пользователей в таблице, то с каждым типом людей в ней работа может замедлиться.Не все ор-сопоставители поддерживают это.

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

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

То, что вы хотите использовать, зависит, конечно, от вашей ситуации.Ни одно из решений не является идеальным, поэтому вам придется взвесить все за и против.

Другие советы

Взгляните на Мартина Фаулера. Шаблоны архитектуры корпоративных приложений:

  • Наследование одной таблицы:

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

  • Наследование таблицы классов:

    Вам нужны структуры базы данных, которые четко сопоставляются с объектами и допускают ссылки в любом месте структуры наследования.Наследование таблиц классов поддерживает это, используя одну таблицу базы данных для каждого класса в структуре наследования.

  • Наследование конкретных таблиц:

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

Если у пользователя, человека и специального человека одинаковые внешние ключи, то у меня будет одна таблица.Добавьте столбец с именем «Тип», который может быть ограничен значениями «Пользователь», «Человек» или «Особый человек».Затем на основе значения Type накладываются ограничения на другие необязательные столбцы.

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

То, что я собираюсь сказать здесь, приведет в замешательство архитекторов баз данных, но вот следующее:

Рассмотрим базу данных вид как эквивалент определения интерфейса.А таблица — это эквивалент класса.

Итак, в вашем примере все классы из трех человек будут реализовывать интерфейс IPerson.Итак, у вас есть 3 таблицы — по одной для каждого из «Пользователь», «Человек» и «Специальный человек».

Затем создайте представление «PersonView» или что-то еще, которое выбирает общие свойства (как определено вашим «интерфейсом») из всех трех таблиц в одно представление.Используйте столбец «PersonType» в этом представлении, чтобы сохранить фактический тип сохраняемого человека.

Поэтому, когда вы выполняете запрос, который можно обрабатывать для любого типа людей, просто запросите представление PersonView.

Возможно, ОП хотел спросить не об этом, но я подумал, что могу добавить это сюда.

Недавно у меня был уникальный случай полиморфизма БД в проекте.У нас было от 60 до 120 возможных классов, каждый со своим собственным набором из 30-40 уникальных атрибутов и примерно 10-12 общих атрибутов для всех классов.Мы решили пойти по пути SQL-XML и в итоге получили одну таблицу.Что-то вроде :

PERSON (personid,persontype, name,address, phone, XMLOtherProperties)

содержащий все общие свойства в виде столбцов, а затем большой пакет свойств XML.Уровень ORM затем отвечал за чтение/запись соответствующих свойств из XMLOtherProperties.Немного как :

 public string StrangeProperty
{
get { return XMLPropertyBag["StrangeProperty"];}
set { XMLPropertyBag["StrangeProperty"]= value;}
}

(в итоге мы сопоставили столбец xml как Hastable, а не как XML-документ, но вы можете использовать то, что лучше всего подходит вашему DAL)

Он не получит никаких наград за дизайн, но будет работать, если у вас большое (или неизвестное) количество возможных классов.А в SQL2005 вы по-прежнему можете использовать XPATH в своих SQL-запросах для выбора строк на основе некоторого свойства, хранящегося в формате XML.это всего лишь небольшое снижение производительности.

Существует три основных стратегии управления наследованием в реляционной базе данных и ряд более сложных/специализированных альтернатив в зависимости от ваших конкретных потребностей.

  • Таблица для каждой иерархии классов.Одна таблица для всей иерархии.
  • Таблица для каждого подкласса.Для каждого подкласса создается отдельная таблица со связью 0-1 между таблицами подклассов.
  • Таблица по конкретному классу.Для каждого конкретного класса создается отдельная таблица.

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

Рискуя стать здесь «космонавтом-архитектурой», я был бы более склонен использовать отдельные таблицы для подклассов.Пусть первичный ключ таблиц подклассов также будет внешним ключом, ссылающимся на супертип.

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

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

Итак, мой дизайн будет чем-то вроде

ЛИЦО (человек, имя, адрес, телефон, ...)

СПЕЦИАЛЬНОЕ ЛИЦО (personid ССЫЛКИ ЧЕЛОВЕКА(personid), дополнительные поля...)

ПОЛЬЗОВАТЕЛЬ (personid ССЫЛКИ PERSON(personid), имя пользователя, зашифрованный пароль, дополнительные поля...)

Вы также можете позже создать VIEW, объединяющие супертип и подтип, если это необходимо.

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

Я бы сказал, что, в зависимости от того, что отличает Person от Special Person, вам, вероятно, не нужен полиморфизм для этой задачи.

Я бы создал таблицу User, таблицу Person, которая имеет поле внешнего ключа, допускающее значение NULL, для пользователя (т. е. человек может быть пользователем, но не обязательно).
Затем я бы создал таблицу SpecialPerson, которая относится к таблице Person с любыми дополнительными полями в ней.Если запись присутствует в SpecialPerson для данного Person.ID, он/она/оно является особым человеком.

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

Я бы использовал таблицу для каждого подкласса, а также избегал бы снижения производительности, но использовал бы ORM, где мы могли бы избежать объединения со всеми таблицами подклассов, создавая запрос на лету на основе типа.Вышеупомянутая стратегия работает для извлечения одного уровня записи, но для массового обновления или выбора вы не можете этого избежать.

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

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

Первый вопрос, который я хотел бы задать, — это отношения между человеком, специальным лицом и пользователем, а также возможно ли, чтобы кто-то был оба специалист и пользователь одновременно.Или любая другая из 4 возможных комбинаций (класс a + b, класс b + c, класс a + c или a + b + c).Если этот класс хранится как значение в type поле и, следовательно, схлопнет эти комбинации, и этот схлоп недопустим, тогда я думаю, что потребуется вторичная таблица, позволяющая установить связь один-ко-многим.Я узнал, что вы не судите об этом, пока не оцените использование и стоимость потери информации о вашей комбинации.

Еще один фактор, который заставляет меня склоняться к одной таблице, — это ваше описание сценария. User является единственным объектом с именем пользователя (скажем, varchar(30)) и паролем (скажем, varchar(32)).Если возможная длина общих полей составляет в среднем 20 символов на 20 полей, то увеличение размера вашего столбца составит 62 по сравнению с 400, или около 15% - 10 лет назад это было бы дороже, чем в современных системах РСУБД, особенно с тип поля, например varchar (например,для MySQL).

И если вас беспокоит безопасность, возможно, было бы полезно иметь вторичную таблицу однозначности под названием credentials ( user_id, username, password).Эта таблица будет вызываться в JOIN контекстуально, скажем, во время входа в систему, но структурно отдельно от «любого» в основной таблице.И, LEFT JOIN доступен для запросов, которые могут рассматриваться как «зарегистрированные пользователи».

Моим главным соображением на протяжении многих лет по-прежнему является рассмотрение значимости объекта (и, следовательно, возможной эволюции) за пределами БД и в реальном мире.В этом случае у всех типов людей есть бьющееся сердце (я надеюсь), а также они могут иметь иерархические отношения друг с другом;поэтому, в глубине души, даже если не сейчас, нам, возможно, придется хранить такие отношения другим методом.Это не имеет прямого отношения к вашему вопросу, но это еще один пример выражения отношений объекта.И к настоящему моменту (7 лет спустя) вы в любом случае должны хорошо понимать, как сработало ваше решение :)

Раньше я делал это именно так, как вы предлагаете: у меня была таблица Person для общих вещей, а затем ссылка SpecialPerson для производного класса.Однако я переосмысливаю это, поскольку Linq2Sql хочет, чтобы поле в той же таблице указывало на разницу.Однако я не слишком внимательно изучал модель сущности — почти уверен, что это позволяет использовать другой метод.

Лично я бы хранил все эти разные классы пользователей в одной таблице.Затем вы можете либо иметь поле, в котором хранится значение «Тип», либо вы можете указать, с каким типом человека вы имеете дело, какие поля заполнены.Например, если UserID имеет значение NULL, эта запись не является пользователем.

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

Первый метод также поддерживается LINQ-to-SQL, если вы решите пойти по этому пути (они называют его «Таблица на иерархию» или «TPH»).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top