Jak radzić sobie z polimorfizmem w bazie danych?

Przykład

Mam Person, SpecialPerson, i User. Person i SpecialPerson to tylko ludzie - nie mają nazwy użytkownika ani hasła na stronie, ale są przechowywane w bazie danych do przechowywania rekordów. Użytkownik ma wszystkie te same dane, Co Person i potencjalnie SpecialPerson, wraz z nazwą użytkownika i hasłem, które są zarejestrowane w witrynie.


Jak rozwiązałbyś ten problem? Czy masz Person tabelę, która przechowuje wszystkie dane wspólne dla osoby i użyć klucza, aby wyszukać swoje dane w SpecialPerson (jeśli są wyjątkową osobą) i Użytkownikiem (jeśli są użytkownikiem) i vice-versa?
Author: Charles Menguy, 2008-09-05

13 answers

Istnieją ogólnie trzy sposoby mapowania dziedziczenia obiektów do tabel bazy danych.

Możesz utworzyć jedną dużą tabelę ze wszystkimi polami ze wszystkich obiektów ze specjalnym polem dla typu. Jest to szybkie, ale marnuje miejsce, chociaż nowoczesne bazy danych oszczędzają miejsce, nie przechowując pustych pól. A jeśli szukasz tylko wszystkich użytkowników w tabeli, z każdym typem osoby w nim rzeczy mogą się spowolnić. Nie wszyscy or-maperzy to popierają.

Możesz tworzyć różne tabele dla wszystkich różne klasy potomne ze wszystkimi tabelami zawierającymi pola klasy podstawowej. To jest ok z punktu widzenia wydajności. Ale nie z punktu widzenia utrzymania. Za każdym razem, gdy twoja klasa bazowa się zmienia, wszystkie tabele się zmieniają.

Możesz również utworzyć tabelę dla każdej klasy, tak jak sugerowałeś. W ten sposób musisz połączyć się, aby uzyskać wszystkie dane. Więc jest mniej wydajny. Myślę, że to najczystsze rozwiązanie.

To, czego chcesz użyć, zależy oczywiście od twojej sytuacji. Żadne z rozwiązań nie jest idealne więc musisz rozważyć plusy i minusy.

 37
Author: Mendelt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-14 01:13:32

# Patrz Martin Fowler Wzorce architektury aplikacji korporacyjnych:

  • Dziedziczenie Pojedynczej Tabeli :

    Podczas mapowania do relacyjnej bazy danych staramy się zminimalizować połączenia, które mogą się szybko montować podczas przetwarzania struktury dziedziczenia w wielu tabelach. Dziedziczenie pojedynczej tabeli mapuje wszystkie pola wszystkich klas struktury dziedziczenia w jedną tabelę.

  • Tabela Klasowa Dziedziczenie :

    Chcesz struktury bazy danych, które wyraźnie odwzorowują obiekty i zezwalają na łącza w dowolnym miejscu struktury dziedziczenia. Dziedziczenie tabeli klas obsługuje to, używając jednej tabeli bazy danych na klasę w strukturze dziedziczenia.

  • Dziedziczenie Tabeli Betonowej :

    Rozważając tabele z punktu widzenia instancji obiektu, rozsądną drogą jest przeniesienie każdego obiektu do pamięci i mapowanie go do jednego wiersza bazy danych. To implikuje konkretne dziedziczenie tabel, gdzie istnieje tabela dla każdej konkretnej klasy w hierarchii dziedziczenia.

 43
Author: Vitor Silva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-14 01:28:32

Jeśli użytkownik, osoba i osoba specjalna mają te same klucze obce, to miałbym jedną tabelę. Dodaj kolumnę o nazwie Typ, która jest ograniczona do Użytkownika, osoby lub osoby specjalnej. Następnie na podstawie wartości Type mają ograniczenia na inne opcjonalne kolumny.

Dla kodu obiektowego nie ma większego znaczenia, jeśli masz oddzielne tabele lub wiele tabel do reprezentowania polimorfizmu. Jednak jeśli trzeba zrobić SQL przeciwko bazie danych, jego znacznie łatwiejsze, jeśli polimorfizm jest ujęty w jednej tabeli...pod warunkiem, że klucze obce dla podtypów są takie same.

 5
Author: Mat Roberts,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 12:25:15

To co tu powiem to prześle architektów baz danych do konspektów ale tu idzie:

Rozważ widok bazy danych jako odpowiednik definicji interfejsu. A tabela jest odpowiednikiem klasy.

Więc w twoim przykładzie wszystkie 3-osobowe klasy zaimplementują interfejs IPerson. Więc masz 3 tabele - po jednym dla każdego z "Użytkownik", "osoba" i "SpecialPerson".

Następnie mieć widok 'PersonView' lub cokolwiek, co wybiera wspólne właściwości (jako zdefiniowany przez twój "interfejs") ze wszystkich 3 tabel w jednym widoku. Użyj kolumny "PersonType" w tym widoku, aby zapisać rzeczywisty typ przechowywanej osoby.

Więc gdy uruchamiasz zapytanie, które można operować na dowolnym typie osoby, po prostu odpytywaj Widok PersonView.

 5
Author: HS.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 12:29:20

Może nie o to chodziło, ale pomyślałem, że wrzucę to tutaj.

Ostatnio miałem unikalny przypadek polimorfizmu db w projekcie. Mieliśmy od 60 do 120 możliwych klas, każda z własnym zestawem 30 do 40 unikalnych atrybutów i około 10-12 wspólnych atrybutów na wszystkich klasach . Zdecydowaliśmy się przejść trasę SQL-XML i skończyło się na jednej tabeli. Coś w stylu:

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

Zawierający wszystkie wspólne właściwości jako kolumny, a następnie dużą właściwość XML torba. Warstwa ORM była następnie odpowiedzialna za Odczyt / Zapis odpowiednich właściwości z Xmlotherperties. Trochę jak:

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

(skończyło się mapowaniem kolumny xml jako Hastable, a nie XML doc, ale możesz użyć tego, co najlepiej pasuje do twojego DAL)

Nie zdobędzie żadnych nagród za wzornictwo, ale zadziała, jeśli masz dużą (lub nieznaną) liczbę możliwych klas. A w SQL2005 nadal możesz używać XPATH w zapytaniach SQL do wybierania wierszy na podstawie jakiejś właściwości, która jest przechowywany jako XML.. to tylko mała kara za występ.

 5
Author: Radu094,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 13:19:38

Istnieją trzy podstawowe strategie obsługi dziedziczenia w relacyjnej bazie danych i wiele bardziej złożonych / dostosowanych alternatyw w zależności od twoich dokładnych potrzeb.

  • Tabela dla hierarchii klas. Jedna tabela dla całej hierarchii.
  • Tabela dla podklasy. Osobna tabela jest tworzona dla każdej podklasy z asocjacją 0-1 między podklasowanymi tabelami.
  • Tabela dla poszczególnych klas betonu. Dla każdej klasy betonu tworzony jest pojedynczy stół.

Każdy z tych appoaches porusza własne problemy dotyczące normalizacji, kodu dostępu do danych i przechowywania danych, chociaż moim osobistym preferowaniem jest użycie tabeli dla podklasy, chyba że istnieje konkretny powód wydajności lub strukturalny, aby wybrać jedną z alternatyw.

 4
Author: Ubiguchi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 12:38:34

Ryzykując bycie "astronautą architektury", byłbym bardziej skłonny wybrać osobne tabele dla podklas. Niech klucz podstawowy tabel podklasy będzie również kluczem obcym łączącym z powrotem do supertype.

Głównym powodem robienia tego w ten sposób jest to, że staje się on bardziej logicznie spójny i nie kończy się na wielu polach, które są NULL i nonsensowne dla danego rekordu. Ta metoda ułatwia również dodawanie dodatkowych pól do podtypów podczas iteracji procesu projektowania.

To dodaje minusa dodawania łączy do zapytań, co może mieć wpływ na wydajność, ale prawie zawsze wybieram najpierw idealny projekt, a następnie optymalizuję później, jeśli okaże się to konieczne. Kilka razy przeszedłem "optymalną" drogę najpierw prawie zawsze żałowałem tego później.

Więc mój projekt byłby czymś w rodzaju

PERSON (personid, name, address, phone, ...)

SPECIALPERSON (personid Referencje PERSON (personid), dodatkowe pola...)

USER (personid reference PERSON (personid), username, encryptedpassword, extra fields...)

Możesz także później tworzyć widoki, które agregują supertyp i podtyp, jeśli jest to konieczne.

Jedyną wadą tego podejścia jest to, że intensywnie poszukujesz podtypów powiązanych z supertypem particulare. Nie ma łatwej odpowiedzi na to z góry mojej głowy, można go śledzić programowo jeśli to konieczne, albo uruchom globalne zapytania soem i buforuj wyniki. To naprawdę zależy od aplikacji.

 4
Author: kaybenleroll,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 12:41:46

Powiedziałbym, że w zależności od tego, co odróżnia osobę od osoby specjalnej, prawdopodobnie nie chcesz polimorfizmu do tego zadania.

Stworzyłbym tabelę User, tabelę Person, która ma nullable foreign key field to User(czyli osoba może być użytkownikiem, ale nie musi).
Wtedy zrobiłbym specjalną tabelę, która odnosi się do tabeli Person z dodatkowymi polami w niej. Jeśli zapis jest obecny w specjalnym dla danego Person.ID to wyjątkowa osoba.

 3
Author: Lars Mæhlum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 12:26:51

W naszej firmie zajmujemy się polimorfizmem, łącząc wszystkie pola w jedną tabelę, a jego najgorsza i niemożliwa do wyegzekwowania integralność referencyjna jest bardzo trudna do zrozumienia. Zdecydowanie polecam takie podejście.

Chciałbym przejść z tabeli na podklasę i również uniknąć hit wydajności, ale za pomocą ORM, gdzie możemy uniknąć łączenia ze wszystkimi tabelami podklasy budując zapytanie w locie na podstawie typu. Powyższa strategia działa na poziomie pojedynczego rekordu, ale w przypadku zbiorczej aktualizacji lub wyboru nie można jej uniknąć.

 2
Author: Kashy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-17 23:06:54

Tak, rozważyłbym również TypeID wraz z tabelą PersonType, jeśli jest to możliwe, że będzie więcej typów. Jednak, jeśli jest tylko 3, które nie powinny być nec.

 1
Author: Sara Chipps,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 12:23:59

To jest starszy post, ale pomyślałem, że ważę się z koncepcyjnego, proceduralnego i performance punktu widzenia.

Pierwsze pytanie, które chciałbym zadać, to relacja między osobą, specjalistą i użytkownikiem, i czy jest możliwe, aby ktoś byłZARÓWNO specjalistą, jak i użytkownikiem jednocześnie. Lub dowolna inna z 4 możliwych kombinacji (Klasa A + b, Klasa b + c, Klasa A + C lub a + b + c). Jeśli ta klasa jest przechowywana jako wartość w polu type i dlatego zwija się te kombinacje, a to załamanie jest nie do przyjęcia, wtedy myślę, że potrzebna byłaby tabela wtórna pozwalająca na relację jeden do wielu. Nauczyłem się, że nie oceniasz tego, dopóki nie oceniasz użycia i kosztów utraty informacji o kombinacji.

Innym czynnikiem, który sprawia, że skłaniam się ku jednej tabeli jest twój opis scenariusza. User jest jedyną jednostką z nazwą użytkownika (powiedzmy varchar (30)) i hasłem(powiedzmy varchar (32)). Jeśli wspólne pola są możliwe długość wynosi średnio 20 znaków na 20 pól, wtedy rozmiar kolumny wzrasta o 62 na 400, czyli o 15% - 10 lat temu byłoby to bardziej kosztowne niż w przypadku nowoczesnych systemów RDBMS, szczególnie w przypadku dostępnego typu pola, takiego jak varchar (np. dla MySQL).

I, jeśli Bezpieczeństwo jest dla Ciebie niepokojące, może być korzystne posiadanie dodatkowej tabeli jeden do jednego o nazwie credentials ( user_id, username, password). Tabela ta będzie wywoływana w kontekście JOIN w czasie logowania, ale strukturalnie oddzielona od tylko "każdy" w głównym stole. A LEFT JOIN jest dostępny dla zapytań, które mogą rozważyć "zarejestrowanych użytkowników".

Moim głównym rozważaniem od lat jest jeszcze rozważenie znaczenia obiektu (a więc ewentualnej ewolucji) poza DB i w świecie rzeczywistym. W tym przypadku wszystkie typy osób mają bijące serca (mam nadzieję), a także mogą mieć hierarchiczne relacje między sobą; więc w głębi mojego umysłu, nawet jeśli nie teraz, możemy potrzebować przechowywać takie relacje przez inna metoda. To nie jest bezpośrednio związane z twoim pytaniem tutaj, ale jest to kolejny przykład wyrażenia relacji obiektu. A teraz (7 lat później) powinieneś mieć dobry wgląd w to, jak twoja decyzja zadziałała:)

 1
Author: Oliver Williams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-28 11:14:56

W przeszłości robiłem to dokładnie tak , jak sugerujesz -- mieć tabelę osób dla zwykłych rzeczy, a następnie specjalne osoby połączone dla klasy pochodnej. Jednak ponownie myślę, że ponieważ Linq2Sql chce mieć pole w tej samej tabeli wskazuje różnicę. Nie przyglądałem się zbytnio modelowi bytu, ale jestem pewien, że pozwala to na inną metodę.

 0
Author: Danimal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 12:21:56

Osobiście przechowywałbym wszystkie te różne klasy użytkowników w jednej tabeli. Możesz wtedy albo mieć pole, które przechowuje wartość "Type", albo możesz zasugerować, z jakim typem masz do czynienia, po tym, jakie pola są wypełnione. Na przykład, jeśli identyfikator UserID jest NULL, to ten rekord nie jest użytkownikiem.

Możesz łączyć się z innymi tabelami za pomocą typu join one to one-or-none, ale wtedy w każdym zapytaniu będziesz dodawać dodatkowe joiny.

Pierwsza metoda jest również obsługiwana przez LINQ-to-SQL jeśli zdecydujesz się przejść tą trasę (nazywają ją 'Table Per hierarchia' lub 'TPH').

 -1
Author: Chris Roberts,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-05 12:20:39