Как вы справляетесь с полиморфизмом в базе данных? - PullRequest
49 голосов
/ 05 сентября 2008

Пример

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


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

Ответы [ 13 ]

46 голосов
/ 05 сентября 2008

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

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

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

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

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

  • Наследование бетонного стола :

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

41 голосов
/ 05 сентября 2008

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

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

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

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

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

6 голосов
/ 05 сентября 2008

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

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

5 голосов
/ 05 сентября 2008

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

Недавно у меня был уникальный случай полиморфизма БД в проекте. У нас было от 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 ... это всего лишь небольшое снижение производительности.

5 голосов
/ 05 сентября 2008

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

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

Итак, в вашем примере все классы из 3 человек будут реализовывать интерфейс IPerson. Таким образом, у вас есть 3 таблицы - по одной для каждого из 'User', 'Person' и 'SpecialPerson'.

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

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

4 голосов
/ 05 сентября 2008

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

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

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

Так что мой дизайн будет выглядеть примерно так:

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

SPECIALPERSON (personid ЛИТЕРАТУРА ЛИЧНОСТЬ (personid), дополнительные поля ...)

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

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

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

4 голосов
/ 05 сентября 2008

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

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

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

3 голосов
/ 05 сентября 2008

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

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

2 голосов
/ 18 июля 2012

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

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

1 голос
/ 28 февраля 2015

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...