2008-09-05 8 views
38

BeispielWie gehen Sie mit Polymorphie in einer Datenbank um?

Ich habe Person, SpecialPerson und User. Person und SpecialPerson sind nur Menschen - sie haben keinen Benutzernamen oder Passwort auf einer Website, aber sie sind in einer Datenbank für die Aufbewahrung von Aufzeichnungen gespeichert. Der Benutzer hat alle die gleichen Daten wie Person und möglicherweise SpecialPerson, zusammen mit einem Benutzernamen und einem Passwort, wie sie mit der Website registriert sind.


Wie würden Sie dieses Problem angehen? Hätten Sie eine Person Tabelle, die alle Daten einer Person speichert und einen Schlüssel verwendet, um ihre Daten in SpecialPerson (wenn sie eine spezielle Person sind) und Benutzer (wenn sie ein Benutzer sind) und umgekehrt?

Antwort

35

Es gibt im Allgemeinen drei Möglichkeiten, die Vererbung von Objekten auf Datenbanktabellen abzubilden.

Sie können eine große Tabelle mit allen Feldern aus allen Objekten mit einem speziellen Feld für den Typ erstellen. Das ist zwar schnell, verschwendet aber Platz, obwohl moderne Datenbanken Platz sparen, indem sie keine leeren Felder speichern.Und wenn Sie nur nach allen Benutzern in der Tabelle suchen, können die Dinge mit jeder Art von Person langsam werden. Nicht alle oder-mapper unterstützen dies.

Sie können für alle untergeordneten Klassen mit allen Tabellen, die die Basisklassenfelder enthalten, verschiedene Tabellen erstellen. Dies ist aus Leistungsperspektive ok. Aber nicht aus einer Wartungsperspektive. Jedes Mal, wenn sich Ihre Basisklasse ändert, ändern sich alle Tabellen.

Sie können auch eine Tabelle pro Klasse erstellen, wie Sie es vorgeschlagen haben. Auf diese Weise benötigen Sie Joins, um alle Daten zu erhalten. Es ist also weniger performant. Ich denke, es ist die sauberste Lösung.

Was Sie verwenden möchten, hängt natürlich von Ihrer Situation ab. Keine der Lösungen ist perfekt, so dass Sie die Vor- und Nachteile abwägen müssen.

+0

Wenn du sagst: "Das ist schnell, aber verschwenderisch", meinst du schnell zu entwickeln oder leistungsmäßig schnell? – user3748908

+0

Beide, alle Daten sind in einer Tabelle (im Gegensatz zu Option 3), so dass keine Joins so ziemlich schnell zu lesen und zu schreiben benötigt, und es ist einfach zu entwickeln. Die meisten OR-Mapper unterstützen solche Schemas. – Mendelt

+0

Schnell im Vergleich zu Table-Per-Type (wegen der Joins), aber langsamer als Table-Per-Concrete, da die Tabellen in Table-Per-Concrete kleiner sind und man sie selten miteinander verbindet, richtig ? – user3748908

0

Persönlich würde ich alle diese verschiedenen Benutzerklassen in einer einzigen Tabelle speichern. Sie können dann entweder ein Feld haben, in dem ein 'Type'-Wert gespeichert ist, oder Sie können angeben, um welche Art von Person es sich handelt, welche Felder ausgefüllt sind. Wenn UserID beispielsweise NULL ist, dann ist dieser Datensatz kein a Benutzer.

Sie könnten zu anderen Tabellen über einen Joins der Kategorie one to one-none verlinken, aber dann fügen Sie in jeder Abfrage zusätzliche Joins hinzu.

Die erste Methode wird auch von LINQ-to-SQL unterstützt, wenn Sie sich für diese Route entscheiden (sie nennen sie "Tabelle pro Hierarchie" oder "TPH").

0

In der Vergangenheit habe ich es genau so gemacht, wie Sie es vorschlagen - haben Sie eine Personentabelle für gängige Dinge, dann SpecialPerson für die abgeleitete Klasse verlinkt. Ich denke jedoch darüber nach, da Linq2Sql ein Feld in der gleichen Tabelle haben möchte, um den Unterschied anzuzeigen. Ich habe das Entity-Modell jedoch nicht zu sehr betrachtet - ziemlich sicher, dass das die andere Methode zulässt.

1

Ja, ich würde auch eine TypeID zusammen mit einer PersonType-Tabelle betrachten, wenn es möglich ist, dass es mehr Typen geben wird. Wenn es jedoch nur 3 gibt, die nicht benötigt werden.

3

Wenn der Benutzer, Person und spezielle Person alle die gleichen Fremdschlüssel haben, dann hätte ich eine einzige Tabelle. Fügen Sie eine Spalte mit dem Namen "Typ" hinzu, die als Benutzer, Person oder spezielle Person definiert ist. Basierend auf dem Wert von Type haben Sie Einschränkungen für die anderen optionalen Spalten.

Für den Objektcode macht es keinen großen Unterschied, ob Sie die einzelnen Tabellen oder mehrere Tabellen für die Darstellung von Polymorphismen haben. Wenn Sie jedoch SQL für die Datenbank ausführen müssen, ist es viel einfacher, wenn der Polymorphismus in einer einzelnen Tabelle erfasst wird ... vorausgesetzt, die Fremdschlüssel für die Untertypen sind identisch.

+0

Einverstanden. "Relationship Driven Design" gilt hier. – bishop

3

Ich würde sagen, dass, je nachdem, was zwischen Person und Special Person unterscheidet, Sie wahrscheinlich keine Polymorphie für diese Aufgabe wollen.

Ich würde eine Benutzer-Tabelle erstellen, eine Person-Tabelle, die ein Nullable-Fremdschlüsselfeld für Benutzer hat (d. H. Die Person kann ein Benutzer sein, muss aber nicht).
Dann würde ich eine SpecialPerson-Tabelle machen, die sich auf die Person-Tabelle mit irgendwelchen zusätzlichen Feldern bezieht. Wenn ein Datensatz in SpecialPerson für eine bestimmte Person.ID vorhanden ist, ist er/sie/es eine besondere Person.

5

Was werde ich hier sagen wird Datenbank-Architekten in conniptions senden, aber hier geht:

eine Datenbank Ansicht als Äquivalent einer Schnittstellendefinition in Betracht. Und eine Tabelle entspricht einer Klasse.

In diesem Beispiel implementieren alle 3-Personen-Klassen die IPerson-Schnittstelle. Sie haben also 3 Tabellen - eine für jeden von 'Benutzer', 'Person' und 'SpecialPerson'.

Dann haben Sie eine Ansicht 'PersonView' oder was auch immer, das die allgemeinen Eigenschaften (wie von Ihrer 'Schnittstelle' definiert) aus allen 3 Tabellen in die Einzelansicht auswählt. Verwenden Sie eine Spalte "PersonType" in dieser Ansicht, um den tatsächlichen Typ der gespeicherten Person zu speichern.

Wenn Sie also eine Abfrage ausführen, die für jede Art von Person ausgeführt werden kann, fragen Sie einfach die PersonView-Ansicht ab.

3

Es gibt drei grundlegende Strategien für die Behandlung der Vererbung in einer relationalen Datenbank und eine Reihe von komplexeren/maßgeschneiderten Alternativen, je nach Ihren genauen Anforderungen.

  • Tabelle pro Klassenhierarchie. Eine Tabelle für die gesamte Hierarchie.
  • Tabelle pro Unterklasse. Eine separate Tabelle wird für jede Unterklasse mit einer 0-1-Verknüpfung zwischen den unterklassierten Tabellen erstellt.
  • Tabelle pro Betonklasse. Eine einzelne Tabelle wird für jede konkrete Klasse erstellt.

Jede dieser appoaches wirft seine eigenen Probleme zu Normalisierung, Datenzugriffscode und Datenspeicherung, obwohl meine persönliche preferance ist Tabelle pro Unterklasse zu verwenden, es sei denn, es gibt eine bestimmte Leistung oder strukturellen Grund zu gehen, mit einem die Alternativen.

2

Auf die Gefahr hin, ein 'Architektur-Astronaut' hier zu sein, würde ich mehr geneigt sein, mit separaten Tabellen für die Unterklassen zu gehen. Lassen Sie den Primärschlüssel der Unterklassentabellen auch einen Fremdschlüssel sein, der mit dem Obertyp verknüpft ist.

Der Hauptgrund dafür ist, dass es dann viel logisch konsistenter wird und Sie nicht mit vielen Feldern enden, die für diesen bestimmten Datensatz NULL und unsinnig sind. Diese Methode macht es auch viel einfacher, den Untertypen zusätzliche Felder hinzuzufügen, wenn Sie Ihren Entwurfsprozess iterieren.

Dies fügt den Nachteil hinzu, JOINs zu Ihren Abfragen hinzuzufügen, was sich auf die Leistung auswirken kann, aber ich gehe fast immer zuerst mit einem idealen Design und dann später zur Optimierung, wenn es sich als notwendig erweist. Die wenigen Male, die ich den "optimalen" Weg gegangen bin, habe ich es fast immer später bereut.

Also mein Design etwas wie

PERSON (PersonID, Name, Adresse, Telefon, ...)

SPECIALPERSON (PersonID BEZUG PERSON (PersonID), zusätzliche Felder ...) wäre

USER (PersonID BEZUG PERSON (PersonID), Benutzername, encryptedpassword, zusätzliche Felder ...)

Sie auch VIEWs später, dass die aggregierte Supertyp und den Subtyp schaffen könnten, wenn dies erforderlich ist.

Der einzige Fehler in diesem Ansatz ist, wenn Sie stark nach Subtypen suchen, die mit einem bestimmten Supertyp verbunden sind. Es gibt keine einfache Antwort auf dieses Problem von oben, Sie könnten es bei Bedarf programmgesteuert verfolgen oder aber globale Abfragen ausführen und die Ergebnisse zwischenspeichern. Es hängt wirklich von der Anwendung ab.

5

Das war vielleicht nicht das, was der OP fragen wollte, aber ich dachte, ich könnte das hier reinwerfen.

Ich hatte kürzlich einen einzigartigen Fall von DB-Polymorphie in einem Projekt. Wir hatten zwischen 60 und 120 möglichen Klassen mit jeweils eigenen 30 bis 40 einzigartigen Attributen und etwa 10 bis 12 gemeinsamen Attributen für alle Klassen. Wir entschieden uns, die SQL-XML-Route zu gehen und landeten mit einer einzigen Tabelle. Etwas wie:

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

enthält alle gemeinsamen Eigenschaften als Spalten und dann eine große XML-Eigenschaft Tasche. Die ORM-Schicht war dann verantwortlich für das Lesen/Schreiben der jeweiligen Eigenschaften aus den XMLOtherProperties. Ein bisschen wie:

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

(am Ende haben wir die Abbildung der XML-Spalte als Hastable up eher als ein XML-Dokument, aber Sie verwenden können, was auch immer Ihre DAL am besten passt)

Es wird keine Designpreise gewinnen , aber es funktioniert, wenn Sie eine große (oder unbekannte) Anzahl von möglichen Klassen haben. Und in SQL2005 können Sie immer noch XPATH in Ihrer SQL-Abfragen verwenden, um Zeilen auszuwählen, auf einer Eigenschaft basiert, die als XML gespeichert ist .. es ist nur eine kleine Leistungseinbuße in nehmen

38

Werfen Sie einen Blick auf Martin Fowler Patterns of Enterprise Application Architecture.:

  • Single Table Inheritance:

    bei der Zuordnung zu einer relationalen Datenbank, versuchen wir, zu minimieren das, dass schnell auffahren schließt sich, wenn in mehreren Tabellen eine Vererbungsstruktur zu verarbeiten. Single Table Inheritance bildet alle Felder aller Klassen einer Vererbungsstruktur in eine einzige Tabelle ab.

  • Class Table Inheritance:

    Sie wollen Datenbankstrukturen, die eindeutig auf die Objekte abzubilden und überall in der Vererbungsstruktur Links ermöglichen. Die Klassen-Tabellenvererbung unterstützt dies, indem in der Vererbungsstruktur eine Datenbanktabelle pro Klasse verwendet wird.

  • Concrete Table Inheritance:

    Denken an Tabellen von einer Objektinstanz Sicht ist ein vernünftiger Weg jedes Objekt im Speicher zu nehmen und es zu einer einzigen Datenbank Zeile abzuzubilden. Dies bedeutet die Vererbung von konkreten Tabellen, wo es für jede konkrete Klasse in der Vererbungshierarchie eine Tabelle gibt.

2

In unserem Unternehmen beschäftigen wir uns mit Polymorphismus durch alle Felder in einer Tabelle kombiniert und seiner schlimmsten und keine referenzielle Integrität erzwungen werden kann und sehr schwer zu verstehen Modell. Ich würde das sicherlich empfehlen.

Ich würde mit Tabelle pro Unterklasse gehen und auch Leistungseinbußen vermeiden, aber mit ORM, wo wir vermeiden können, mit allen Unterklasse Tabellen durch Erstellen von Abfrage im laufenden Betrieb basierend auf Typ zu verbinden. Die vorgenannte Strategie funktioniert für Single-Record-Level-Pull, aber für Bulk-Update oder Auswahl können Sie es nicht vermeiden.

1

Dies ist ein älterer Beitrag, aber ich dachte, ich werde aus einem konzeptionellen, prozeduralen und Performance-Standpunkt abwägen.

Die erste Frage, die ich stellen möchte, ist die Beziehung zwischen Person, Sonderperson und Benutzer, und ob es möglich ist, dass jemand gleichzeitig eine spezielle Person und ein Benutzer ist. Oder jede andere von 4 möglichen Kombinationen (Klasse a + b, Klasse b + c, Klasse a + c oder a + b + c). Wenn diese Klasse als Wert in einem type-Feld gespeichert wird und daher diese Kombinationen zusammenbrechen würde, und dieser Zusammenbruch nicht akzeptabel ist, würde ich denken, dass eine sekundäre Tabelle erforderlich wäre, die eine Eins-zu-viele-Beziehung zulässt. Ich habe gelernt, dass Sie das nicht beurteilen, bis Sie die Nutzung und die Kosten für den Verlust Ihrer Kombinationsinformationen bewertet haben.

Der andere Faktor, der mich zu einer einzigen Tabelle neigt, ist Ihre Beschreibung des Szenarios. User ist die einzige Entität mit einem Benutzernamen (z. B. varchar (30)) und einem Kennwort (z. B. varchar (32)). Beträgt die mögliche Länge der allgemeinen Felder durchschnittlich 20 Zeichen pro 20 Felder, dann ist Ihre Spaltengrößenzunahme 62 über 400 oder etwa 15% - vor 10 Jahren wäre dies teurer gewesen als bei modernen RDBMS-Systemen, insbesondere mit Ein Feldtyp wie varchar (zB für MySQL) ist verfügbar.

Und wenn die Sicherheit für Sie von Interesse ist, kann es vorteilhaft sein, eine zweite eins-zu-eins-Tabelle mit der Bezeichnung credentials (user_id, username, password) zu haben. Diese Tabelle würde in einem JOIN kontextuell zum gegebenen Zeitpunkt der Anmeldung aufgerufen, aber strukturell von nur "irgendjemand" in der Haupttabelle getrennt. Und eine LEFT JOIN ist für Abfragen verfügbar, die "registrierte Benutzer" berücksichtigen könnten.

Meine wichtigste Überlegung für Jahre ist immer noch, die Bedeutung des Objekts (und damit mögliche Evolution) außerhalb der DB und in der realen Welt zu betrachten. In diesem Fall haben alle Arten von Menschen schlagende Herzen (ich hoffe) und kann auch hierarchische Beziehungen zueinander haben; Im Hinterkopf müssen wir, wenn auch nicht jetzt, solche Beziehungen auf andere Weise speichern.Das hängt nicht explizit mit Ihrer Frage hier zusammen, aber es ist ein weiteres Beispiel für den Ausdruck der Beziehung eines Objekts. Und jetzt (7 Jahre später) sollten Sie einen guten Einblick haben, wie Ihre Entscheidung trotzdem funktioniert hat :)

Verwandte Themen