Dziedziczenie - baza danych

0

Witam,

Pisze prostą aplikację do tworzenia ankiet i egzaminów.
Sprawa jest dość prosta: Egzamin to tak naprawdę ankieta z odpowiedziami A, B, C, D. jedyna różnica to ta, że X odpowiedzi jest prawidłowa. W ankiecie ich zaś brak.

W kodzie, więc zrobiłem dziedziczenie
class Exam : Questinnaire

I odpowiednie metody itd. Do tego momentu sprawa jest oczywista.
Problem pojawia się w bazie danych. Jaką strukturę powinien wykonać by był taki zapis optymalny i prawidłowy?

Czy powinienem zrobić tylko jedną tabelę i do niej dodać kolumnę "symbol" i po niej poznawać, czy mam do czynienia z ankietą lub egzaminem (nie podobna mi się takie rozwiązanie), czy też może lepiej zrobić dwie tabele "Ankieta" i "Egzamin"? Jednak co w takiej sytuacji powinno znaleźć się w tabeli egzamin? Bo ja widze w takim przypadku jedynie ID oraz IDQuesttionaire. W przeciwnym wypadku musiałbym skopiować właściwie dokładnie te same pola z ankiet.

Jak to dobrze zrobić?
Pozdrawiam.

1

Optymalnego rozwiązania nie ma. Generalnie masz trzy podejścia:

  1. Table Per Hierarchy - czyli jedna tabela z kolumną dyskryminującą i kolumnami dla wszystkich pól całej hierarchii klas.
  2. Table Per Type - czyli jedna tabela z kolumnami na pola klasy bazowej, i kolejne tabele zawierające kolumny odpowiadające polom klas dziedziczących.
  3. Table Per Class - czyli każda klasa ma swoją tabelę z kolumnami dla wszystkich pól (także tych z klasy bazowej).

Z uwagi na małe różnice między klasami, w Twoim przypadku zastosowałbym TPH. I oczywiście nie robiłbym tego ręcznie, od tego są ORMy.

0

Dziękuję bardzo za odpowiedź. ORM czasami nie wchodzą w grę, w tym przypadku byłoby to otwieranie armaty do muchy. Ogólnie znam Entity Framework, ale też pewne założenia na niskim poziomie abstrakcji także warto znać.

Również podobnie jak Ty skłaniałbym się do TPH - nawet wstępnie tak zrobiłem, zastanawiam się tylko jak w aplikacji w takiej sytuacji ładnie wczytać dane? Niepokoi mnie również możliwość ewentualnego rozwinięcia się różnic pomiędzy ankietą a egzaminem, a wtedy wiązałoby się to z przebudową tabel i aplikacji.

Mam uniwersalną metodę:

        public void Load(int id)
        {
            this.CopyObject(this.operationData.Load(id));
        }

gdzie funkcja Load zwraca obiekt biznesowy (zwraca klasę bazową dla obiektów biznesowych) natomiast w metodzie CopyObject następuje mapowanie na faktyczny wynikowy obiekt.

W takiej sytuacji w kodzie mam dwa rozwiązania:
1 - Używam jednej klasy "Ankieta" do stworzenia ankiet i egzaminów przez co mam nadmiar funkcji w klasie (chociażby o jedną metodę "VerifyExam()").
2 - W kodzie rozróżniam ankiety i egzaminy, ale wtedy na etapie wczytywania danych nie wiem jaki typ danych zwraca mi klasa Load - muszę wykonać instrukcje warunkowe lub switch, by to sprawdzić. Nie będzie to estetyczne. Dodatkowo powoduje to problemy przy ewentualnych raportach gdzie na jednej liście miałyby być ankiety i egzaminy.

Zastanawia mnie tutaj jak wyjść w takiej sytuacji z dobrym pomysłem. ORM też ma swoje wady i wcale nie jest lekiem na każde zło.
Pomińmy na potrzeby tego problemu ORM. Wiem, że to wygodne, ale nie chcę tego używać w tym projekcie.

0

Po długich rozmyślaniach podjąłem decyzję, iż zastosuję jednak TPT.
Duże ryzyko wystąpienia pól rozszerzających jest powodem tej decyzji.

Rozpoznawać zaś rodzaj ankiety / egzaminu będę po typie zapisanym na bazie - bez użycia instrukcji warunkowych a jedynie przy użyciu refleksji.
Takie zastosowanie jest uniwersalne, pozwalające na szybkie rozszerzenie aplikacji. Niemniej Twój post mi pomógł gdyż mogłem rozważyć szybko wszelkie wady i zalety.

1
Pijany Terrorysta napisał(a):

Niepokoi mnie również możliwość ewentualnego rozwinięcia się różnic pomiędzy ankietą a egzaminem, a wtedy wiązałoby się to z przebudową tabel i aplikacji.

Przecież różnice będą powstawały niezależnie od tego, który model wybierzesz...

Mam uniwersalną metodę:

        public void Load(int id)
        {
            this.CopyObject(this.operationData.Load(id));
        }

gdzie funkcja Load zwraca obiekt biznesowy (zwraca klasę bazową dla obiektów biznesowych) natomiast w metodzie CopyObject następuje mapowanie na faktyczny wynikowy obiekt.

Jedno jest pewne Load niczego nie zwraca.

W takiej sytuacji w kodzie mam dwa rozwiązania:
1 - Używam jednej klasy "Ankieta" do stworzenia ankiet i egzaminów przez co mam nadmiar funkcji w klasie (chociażby o jedną metodę "VerifyExam()").
2 - W kodzie rozróżniam ankiety i egzaminy, ale wtedy na etapie wczytywania danych nie wiem jaki typ danych zwraca mi klasa Load - muszę wykonać instrukcje warunkowe lub switch, by to sprawdzić. Nie będzie to estetyczne. Dodatkowo powoduje to problemy przy ewentualnych raportach gdzie na jednej liście miałyby być ankiety i egzaminy.

Rozwiązanie 1 nie ma obiektowego sensu, więc nie ma sensu go rozpatrywać.
Rozwiązanie 2 - nie rozumiem problemu. Wczytujesz rekord z tabeli, w zależności od wartości kolumny opisującej typ tworzysz obiekt klasy X albo Y (przez refleksję), i wstawiasz do niego wartości z pozostałych kolumn.
Drugi sposób - niech Twoja metoda będzie generyczna, wtedy od razu będzie wiedziała jaki obiekt utworzyć, a następnie uzupełnisz go wartościami z kolumn dla rekordu o podanym w argumencie ID.

ORM też ma swoje wady i wcale nie jest lekiem na każde zło.

O ile nie potrzebujesz super wydajności czy hurtowego wstawiania ogromnych ilości danych, to owszem, jest!
Na dodatek, użycie ORMa zajęłoby mniej czasu i znaków niż Twój post z pytaniem, a poza tym pozwoliłoby w każdej chwili na zmianę sposobu mapowania, gdybyś stwierdził, że coś Ci w którymś nie pasuje. Nigdy nie warto wynajdować koła na nowo.

Pijany Terrorysta napisał(a):

Po długich rozmyślaniach podjąłem decyzję, iż zastosuję jednak TPT.
Duże ryzyko wystąpienia pól rozszerzających jest powodem tej decyzji.

Mam nadzieję, że zdajesz sobie sprawę, że ten sposób jest najmniej wydajny.

Rozpoznawać zaś rodzaj ankiety / egzaminu będę po typie zapisanym na bazie - bez użycia instrukcji warunkowych a jedynie przy użyciu refleksji.

Co, jak już pisałem, mógłbyś zrobić też w przypadku TPH.

0

Przecież różnice będą powstawały niezależnie od tego, który model wybierzesz...

Tak, ale w którym łatwiej wprowadzić zmiany?
Załóżmy, że powstanie 5 nowych kolumn dla egzaminu, wtedy dla ankiety miałbym aż 5 kolumn nadmiarowych. Niezbyt estetyczne.

Rozwiązanie 2 - nie rozumiem problemu. Wczytujesz rekord z tabeli, w zależności od wartości kolumny opisującej typ tworzysz obiekt klasy X albo Y (przez refleksję), i wstawiasz do niego wartości z pozostałych kolumn.
Drugi sposób - niech Twoja metoda będzie generyczna, wtedy od razu będzie wiedziała jaki obiekt utworzyć, a następnie uzupełnisz go wartościami z kolumn dla rekordu o podanym w argumencie ID.

No widzisz w powyższym poście właśnie do takich wniosków doszedłem :)

Także dziękuję za pomoc, a ORM w tym przypadku nie jest możliwy z powodów technologicznych. Tzn. stary system.

0
Pijany Terrorysta napisał(a):

Załóżmy, że powstanie 5 nowych kolumn dla egzaminu, wtedy dla ankiety miałbym aż 5 kolumn nadmiarowych. Niezbyt estetyczne.

Dlatego TPH jest lepsze gdy klas jest mało i różnice między nimi nie są duże.

No widzisz w powyższym poście właśnie do takich wniosków doszedłem :)

No nie, bo Ty chcesz zastosować TPT, a ja pisałem, że nawet TPH nie wymaga żadnych ifów.

0

Dlatego TPH jest lepsze gdy klas jest mało i różnice między nimi nie są duże.

No własnie tylko nie sposób przewidzieć, czy tabela się nie rozwinie na tyle, by to miało nadal sens. Lepiej od razu zrobić rozwojowo.

No nie, bo Ty chcesz zastosować TPT, a ja pisałem, że nawet TPH nie wymaga żadnych ifów.

Tak, wiem o tym. TPH jest ok, ale boję się, że powstanie za dużo nowych kolumn tylko dla egzaminu (np. stopień trudności, waga, punkty, komisja i jeszcze inne cuda). W związku z tym lepiej zrobić TPT.

0
Pijany Terrorysta napisał(a):

Tak, wiem o tym. TPH jest ok, ale boję się, że powstanie za dużo nowych kolumn tylko dla egzaminu (np. stopień trudności, waga, punkty, komisja i jeszcze inne cuda). W związku z tym lepiej zrobić TPT.

Jak uważasz, ja bym się raczej skłaniał ku TPC, żeby nie joinować niepotrzebnie.

0

Jeszcze małe pytanie bym miał 100% pewności, że wiem na co się pisze :)

Jeśli podejmę się dziedziczenia z zachowaniem modelu obiektowego na bazie danych (TPT) to moje zapytanie:
1.) Wyszukujące egzaminy
Będzie musiało joinować się do ankiet w stylu

Select * from ankiety quest, Egzaminy exam
   WHERE quest.id = exam.id

2.) Wyszukujące ankiety

SELECT * FROM ankiety quest
     WHERE NOT EXISTS (SELECT * FROM Egzaminy exam
       WHERE exam.id = quest.id);

Dobrze rozumiem prawda?

TPC zbyt brzydko mi pachnie strukturalnym modelowaniem.
TPT jest mało wydajne przy zapytaniach
TPH zaś jest niebezpieczny jeśli nastąpi potrzeba rozszerzenia

1

Zakładając, że mamy klasy: abstract QuestionnaireBase oraz Exam : QuestionnaireBase i StandardQuestionnaire : QuestionnaireBase oraz tabele o takich samych nazwach, wystarczą takie zapytania:

SELECT * FROM Questionnaire Q JOIN QuestionnaireBase B ON B.Id = Q.Id
SELECT * FROM Exam E JOIN QuestionnaireBase B ON B.Id = E.Id

I nie trzeba nic kombinować z jakimiś wherami i podzapytaniami.

0

Ok dziękuję bardzo.

1 użytkowników online, w tym zalogowanych: 0, gości: 1