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





Лично я бы сохранил все эти разные классы пользователей в одной таблице. Затем у вас может быть поле, в котором хранится значение «Тип», или вы можете подразумевать, с каким типом человека вы имеете дело, по заполненным полям. Например, если UserID равен NULL, то эта запись не является Пользователь.
Вы можете связываться с другими таблицами, используя тип соединения один к одному или ни одного, но тогда в каждом запросе вы будете добавлять дополнительные соединения.
Первый метод также поддерживается LINQ-to-SQL, если вы решите пойти по этому маршруту (они называют его «Таблица на иерархию» или «TPH»).
Раньше я делал это именно так, как вы предлагаете - имел таблицу Person для обычных вещей, а затем SpecialPerson, связанный с производным классом. Однако я снова думаю, что, поскольку Linq2Sql хочет, чтобы поле в той же таблице указывало на разницу. Однако я не слишком много смотрел на модель сущности - почти уверен, что это допускает другой метод.
да, я бы также рассмотрел TypeID вместе с таблицей PersonType, если возможно, будет больше типов. Однако, если их всего 3, они не должны быть исключены.
Если пользователь, лицо и специальное лицо имеют одинаковые внешние ключи, то у меня будет одна таблица. Добавьте столбец «Тип», который может быть «Пользователь», «Лицо» или «Специальное лицо». Затем на основе значения Типа есть ограничения для других необязательных столбцов.
Для объектного кода не имеет большого значения, есть ли у вас отдельные таблицы или несколько таблиц для представления полиморфизма. Однако, если вам нужно выполнить SQL для базы данных, это будет намного проще, если полиморфизм будет зафиксирован в одной таблице ... при условии, что внешние ключи для подтипов одинаковы.
Предположение, что наличие у типов общих внешних ключей должно влиять на ваш выбор, является хорошим аргументом.
Я бы сказал, что в зависимости от того, что отличает человека от особого человека, вам, вероятно, не нужен полиморфизм для этой задачи.
Я бы создал таблицу User, таблицу Person, в которой есть поле внешнего ключа, допускающее значение NULL, для User (т. Е. Person может быть пользователем, но не обязательно). Затем я бы сделал таблицу SpecialPerson, которая связана с таблицей Person с любыми дополнительными полями в ней. Если запись для данного Person.ID присутствует в SpecialPerson, он / она / она является особым лицом.
Обычно существует три способа сопоставления наследования объектов с таблицами базы данных.
Вы можете составить одну большую таблицу со всеми полями из всех объектов со специальным полем для типа. Это быстро, но тратит впустую место, хотя современные базы данных экономят место, не сохраняя пустые поля. И если вы ищете только всех пользователей в таблице, с каждым типом людей в ней работа может замедлиться. Не все or-mappers поддерживают это.
Вы можете создавать разные таблицы для всех различных дочерних классов со всеми таблицами, содержащими поля базового класса. Это нормально с точки зрения производительности. Но не с точки зрения обслуживания. Каждый раз, когда ваш базовый класс изменяется, все таблицы меняются.
Вы также можете создать таблицу для каждого класса, как вы предложили. Таким образом, вам понадобятся объединения, чтобы получить все данные. Так что это менее производительно. Думаю, это самое чистое решение.
То, что вы хотите использовать, конечно же, зависит от вашей ситуации. Ни одно из решений не является идеальным, поэтому вам нужно взвесить все за и против.
Когда вы говорите это быстро, но тратит пространство, вы имеете в виду быстрое развитие или быстрое с точки зрения производительности?
В обоих случаях все данные находятся в одной таблице (в отличие от варианта 3), поэтому никаких объединений не требуется, достаточно быстро для чтения и записи, и это легко разрабатывать. Большинство OR-картографов поддерживают подобные схемы.
Быстро по сравнению с Table-Per-Type (из-за объединений), но медленнее, чем Table-Per-Concrete, поскольку таблицы в Table-Per-Concrete меньше, и вы редко собираетесь соединять их друг с другом, верно?
Определенно быстро по сравнению с Table-Per-Type, вероятно, сравнимым с Table-Per-Concrete. Я был бы очень удивлен, если бы выбор стал медленнее, если бы в таблице были дополнительные столбцы, которые не являются частью этого оператора выбора.
Возможно, четвертый вариант - использовать базу данных NoSQL, т. Е. MongoDB?
Даже в нереляционной базе данных или базе данных NoSQL вам нужно решить, как хранить набор объектов и их полей, и эти три стратегии по-прежнему имеют смысл. В базе данных документов, такой как MongoDB, вариант 1 очень хорошо соответствует структуре базы данных. Например, в хранилище столбцов или графической базе данных другие варианты могут иметь больше смысла, но использование NoSQL само по себе не является решением.
То, что я собираюсь здесь сказать, заставит архитекторов баз данных задуматься, но вот что:
Считайте базу данных Посмотреть эквивалентом определения интерфейса. А таблица - это эквивалент класса.
Итак, в вашем примере все 3 класса будут реализовывать интерфейс IPerson. Итак, у вас есть 3 таблицы - по одной для каждого из «User», «Person» и «SpecialPerson».
Затем у вас есть представление «PersonView» или что-то еще, которое выбирает общие свойства (как определено вашим «интерфейсом») из всех трех таблиц в едином представлении. Используйте столбец «PersonType» в этом представлении, чтобы сохранить фактический тип сохраняемого человека.
Поэтому, когда вы выполняете запрос, который может быть обработан для любого типа человека, просто запросите представление PersonView.
Существует три основных стратегии обработки наследования в реляционной базе данных и ряд более сложных / индивидуальных альтернатив в зависимости от ваших конкретных потребностей.
Каждый из этих подходов поднимает свои собственные проблемы, связанные с нормализацией, кодом доступа к данным и хранением данных, хотя я лично предпочитаю использовать таблица на подкласс, если нет конкретной производственной или структурной причины для использования одной из альтернатив.
Рискуя оказаться здесь «астронавтом-архитектором», я был бы более склонен использовать отдельные таблицы для подклассов. Первичный ключ таблиц подкласса также должен быть внешним ключом, связывающимся с супертипом.
Основная причина этого заключается в том, что тогда он становится более логически последовательным, и вы не получаете много полей, которые являются NULL и бессмысленными для этой конкретной записи. Этот метод также значительно упрощает добавление дополнительных полей к подтипам при повторении процесса проектирования.
Это действительно добавляет обратную сторону добавления JOIN к вашим запросам, что может повлиять на производительность, но я почти всегда сначала выбираю идеальный дизайн, а затем смотрю на оптимизацию позже, если это окажется необходимым. Несколько раз я сначала шел «оптимальным» путем, а потом почти всегда сожалел об этом.
Так что мой дизайн был бы похож на
ЛИЦО (лицо, имя, адрес, телефон, ...)
СПЕЦИАЛЬНОЕ ЛИЦО (personid ССЫЛКИ (personid), дополнительные поля ...)
ПОЛЬЗОВАТЕЛЬ (personid ССЫЛКИ (personid), имя пользователя, зашифрованный пароль, дополнительные поля ...)
Позже вы также можете создать представления, объединяющие супертип и подтип, если это необходимо.
Единственный недостаток этого подхода заключается в том, что вы интенсивно ищете подтипы, связанные с определенным супертипом. На этот вопрос нет простого ответа, вы можете отслеживать его программно, если необходимо, или запускать глобальные запросы и кэшировать результаты. Это действительно будет зависеть от приложения.
Возможно, ОП хотел спросить не об этом, но я подумал, что могу бросить это здесь.
Недавно у меня был уникальный случай полиморфизма БД в проекте. У нас было от 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 ... это просто небольшое снижение производительности.
Взгляните на Паттерны архитектуры корпоративных приложений Мартина Фаулера:
When mapping to a relational database, we try to minimize the joins that can quickly mount up when processing an inheritance structure in multiple tables. Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.
You want database structures that map clearly to the objects and allow links anywhere in the inheritance structure. Class Table Inheritance supports this by using one database table per class in the inheritance structure.
Наследование бетонной таблицы:
Thinking of tables from an object instance point of view, a sensible route is to take each object in memory and map it to a single database row. This implies Concrete Table Inheritance, where there's a table for each concrete class in the inheritance hierarchy.
В нашей компании мы имеем дело с полиморфизмом, комбинируя все поля в одной таблице, и это худшее, и никакая ссылочная целостность не может быть навязана и очень трудная для понимания модель. Я бы не рекомендовал такой подход.
Я бы пошел с таблицей для каждого подкласса, а также избегал бы снижения производительности, но с использованием ORM, где мы можем избежать объединения со всеми таблицами подкласса, создавая запрос на лету, основываясь на типе. Вышеупомянутая стратегия работает для извлечения на уровне одной записи, но для массового обновления или выбора вы не можете этого избежать.
Это старый пост, но я подумал, что взвесу его с концептуальной, процедурной точки зрения и с точки зрения производительности.
Первый вопрос, который я хотел бы задать, - это отношения между человеком, особым лицом и пользователем, и возможно ли, чтобы кто-то был обе особым лицом и пользователем одновременно. Или любую другую из 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 лет спустя) вы должны хорошо понимать, как ваше решение сработало в любом случае :)
Согласовано. Здесь применяется «дизайн, основанный на отношениях».