Comment gérez-vous le polymorphisme dans une base de données?

Exemple

J'ai Person, SpecialPerson, et User. Person et SpecialPerson ne sont que des personnes - elles n'ont pas de nom d'utilisateur ou de mot de passe sur un site, mais elles sont stockées dans une base de données pour la tenue de dossiers. L'Utilisateur a toutes les mêmes données que Person et potentiellement SpecialPerson, ainsi qu'un nom d'utilisateur et un mot de passe lorsqu'ils sont enregistrés sur le site.


Comment aborderiez-vous ce problème? Auriez - vous une table Person qui stocke toutes les données communes à une personne et utilise une clé pour rechercher ses données dans SpecialPerson (s'ils sont une personne spéciale) et utilisateur (s'ils sont un utilisateur) et vice-versa?

44
demandé sur Charles Menguy 2008-09-05 16:16:12

13 réponses

Il existe généralement trois façons de mapper l'héritage d'objets aux tables de base de données.

Vous pouvez créer une grande table avec tous les champs de tous les objets avec un champ spécial pour le type. C'est rapide mais gaspille de l'espace, bien que les bases de données modernes économisent de l'espace en ne stockant pas les champs vides. Et si vous ne cherchez que tous les utilisateurs dans la table, avec chaque type de personne, les choses peuvent être lentes. Tous les Or-mappers ne supportent pas cela.

Vous pouvez créer différentes tables pour différentes classes enfants avec toutes les tables contenant les champs de classe de base. C'est ok du point de vue de la performance. Mais pas du point de vue de la maintenance. Chaque fois que votre classe de base Change, toutes les tables changent.

Vous pouvez également créer une table par classe comme vous l'avez suggéré. De cette façon, vous avez besoin de jointures pour obtenir toutes les données. Donc c'est moins performant. Je pense que c'est la solution la plus propre.

Ce que vous voulez utiliser dépend bien sûr de votre situation. Aucune des solutions n'est parfaite donc, vous devez peser le pour et le contre.

37
répondu Mendelt 2016-03-14 01:13:32

Jetez un oeil à Martin Fowler modèles D'Architecture D'Application D'entreprise:

  • Héritage De Table Unique :

    Lors du mappage vers une base de données relationnelle, nous essayons de minimiser les jointures qui peuvent rapidement monter lors du traitement d'une structure d'héritage dans plusieurs tables. L'héritage D'une Table unique mappe tous les champs de toutes les classes d'une structure d'héritage en une seule table.

  • Tableau Des Classes Héritage :

    Vous voulez des structures de base de données qui correspondent clairement aux objets et permettent des liens n'importe où dans la structure d'héritage. Class table Inheritance prend en charge cela en utilisant une table de base de données par classe dans la structure d'héritage.

  • Héritage De Table En Béton :

    En pensant aux tables du point de vue d'une instance d'objet, une route raisonnable consiste à prendre chaque objet en mémoire et à le mapper sur une seule ligne de base de données. Ce implique L'héritage de Table concrète, où il y a une table pour chaque classe concrète dans la hiérarchie d'héritage.

43
répondu Vitor Silva 2016-03-14 01:28:32

Si L'utilisateur, la personne et la personne spéciale ont tous les mêmes clés étrangères, alors j'aurais une seule table. Ajoutez une colonne appelée Type qui est contrainte D'être Utilisateur, personne ou personne spéciale. Ensuite, en fonction de la valeur de Type ont des contraintes sur les autres colonnes facultatives.

Pour le code objet, cela ne fait pas beaucoup de différence si vous avez les tables séparées ou plusieurs tables pour représenter le polymorphisme. Cependant, si vous devez faire SQL contre la base de données, c'est beaucoup plus facile si le le polymorphisme est capturé dans une seule table...fourni les clés étrangères pour les sous-types sont les mêmes.

5
répondu Mat Roberts 2008-09-05 12:25:15

Ce que je vais dire ici va envoyer des architectes de base de données dans des conniptions Mais voici:

Prenons une base de données afficher comme l'équivalent d'une définition d'interface. Et d'un tableau est l'équivalent d'une classe.

Donc, dans votre exemple, toutes les classes de 3 personnes implémenteront L'interface IPerson. Vous avez donc 3 tables-une pour chaque 'User',' Person ' et 'SpecialPerson'.

Alors avoir une vue 'PersonView' ou quoi que ce soit qui sélectionne les propriétés communes (comme défini par votre 'interface') à partir des 3 tables dans la vue unique. Utilisez une colonne' PersonType ' dans cette vue pour stocker le type réel de la personne stockée.

Donc, lorsque vous exécutez une requête qui peut être utilisée sur n'importe quel type de personne, il suffit d'interroger la vue PersonView.

5
répondu HS. 2008-09-05 12:29:20

Ce n'est peut-être pas ce que l'OP voulait demander, mais j'ai pensé que je pourrais jeter ceci ici.

J'ai récemment eu un cas unique de polymorphisme db dans un projet. Nous avions entre 60 et 120 classes possibles, chacune avec son propre ensemble de 30 à 40 attributs uniques, et environ 10 à 12 attributs communs sur toutes les classes . Nous avons décidé d'aller sur la route SQL-XML et nous avons fini avec une seule table. Quelque chose comme:

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

Contenant toutes les propriétés communes sous forme de Colonnes, puis une grande propriété XML sac. La couche ORM était alors responsable de la lecture/écriture des propriétés respectives à partir des propriétés XMLOtherProperties. Un peu comme:

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

(nous avons fini par mapper la colonne xml comme Hastable plutôt que comme document XML, mais vous pouvez utiliser ce qui convient le mieux à votre DAL)

Il ne va pas gagner de prix de design, mais cela fonctionnera si vous avez un grand nombre (ou inconnu) de classes possibles. Et dans SQL2005 vous pouvez toujours utiliser XPATH dans vos requêtes SQL pour sélectionner des lignes en fonction de certaines propriétés sont stockées au format XML.. c'est juste une petite pénalité de performance à prendre.

5
répondu Radu094 2008-09-05 13:19:38

Il existe trois stratégies de base pour gérer l'héritage dans une base de données relationnelle, et un certain nombre d'alternatives plus complexes/sur mesure en fonction de vos besoins exacts.

  • Table par hiérarchie de classe. Une table pour toute la hiérarchie.
  • Tableau par sous-classe. Une table séparée est créée pour chaque sous-classe avec une association 0-1 entre les tables sous-classées.
  • Tableau par classe de béton. Une seule table est créée pour chaque classe concrète.

Chacun de ces appoaches soulève ses propres problèmes de normalisation, de code d'accès aux données et de stockage de données, bien que ma préférence personnelle soit d'utiliser table par sous-classe à moins qu'il n'y ait une performance spécifique ou une raison structurelle pour aller avec l'une des alternatives.

4
répondu Ubiguchi 2008-09-05 12:38:34

Au risque d'être un "astronaute d'architecture" ici, je serais plus enclin à aller avec des tables séparées pour les sous-classes. La clé primaire des tables de sous-classe doit également être une clé étrangère reliant le supertype.

La principale raison de le faire de cette façon est qu'il devient alors beaucoup plus logique et que vous ne vous retrouvez pas avec beaucoup de champs qui sont nuls et absurdes pour cet enregistrement particulier. Cette méthode rend également beaucoup plus facile d'ajouter des champs supplémentaires aux sous-types lorsque vous itérez votre processus de conception.

Cela ajoute l'inconvénient d'ajouter des jointures à vos requêtes, ce qui peut avoir un impact sur les performances, mais je vais presque toujours d'abord avec un design idéal, puis chercher à optimiser plus tard si cela s'avère nécessaire. Les quelques fois où je suis allé de la manière "optimale" d'abord, je l'ai presque toujours regretté plus tard.

Donc mon design serait quelque chose comme

Personne (personid, nom, adresse, téléphone, ...)

Personne spéciale (personid Références personne (personid), champs supplémentaires...)

Utilisateur (PersonID références personne (personid), nom d'utilisateur, encryptedpassword, champs supplémentaires...)

Vous pouvez également créer des vues plus tard qui agrège le supertype et le sous-Type, si cela est nécessaire.

Le seul défaut de cette approche est si vous vous trouvez fortement à la recherche des sous-types associés à un supertype particulare. Il n'y a pas de réponse facile à cela du haut de ma tête, vous pouvez le suivre par programme si nécessaire, ou bien exécutez des requêtes globales soem et mettez en cache les résultats. Cela dépendra vraiment de l'application.

4
répondu kaybenleroll 2008-09-05 12:41:46

Je dirais que, selon ce qui différencie Person et Special Person, vous ne voulez probablement pas de polymorphisme pour cette tâche.

Je créerais une table User, une table Person qui a un champ de clé étrangère nullable pour L'utilisateur (c'est-à-dire que la personne peut être un utilisateur, mais n'a pas à le faire).
Ensuite, je ferais une table SpecialPerson qui se rapporte à la table Person avec des champs supplémentaires. Si un enregistrement est présent dans SpecialPerson pour une donnée Person.ID, il/elle / il est une personne spéciale.

3
répondu Lars Mæhlum 2008-09-05 12:26:51

Dans notre société, Nous traitons du polymorphisme en combinant tous les champs dans une table et son pire et aucune intégrité référentielle ne peut être appliquée et très difficile à comprendre le modèle. Je recommanderais contre cette approche à coup sûr.

J'irais avec Table par sous-classe et éviterais également les performances mais en utilisant ORM où nous pouvons éviter de nous joindre à toutes les tables de sous-classe en construisant la requête à la volée en se basant sur le type. La stratégie susmentionnée fonctionne pour l'extraction de niveau d'enregistrement unique mais pour la mise à jour en vrac ou sélectionnez, vous ne pouvez pas l'éviter.

2
répondu Kashy 2012-07-17 23:06:54

Oui, je considérerais aussi un TypeID avec une table PersonType s'il est possible qu'il y ait plus de types. Cependant, s'il n'y a que 3 cela ne devrait pas être nec.

1
répondu Sara Chipps 2008-09-05 12:23:59

Ceci est un poste plus ancien, mais je pensais que je vais peser d'un point de vue conceptuel, procédural et de performance.

La première question que je poserais est la relation entre person, specialperson et user, et s'il est possible pour quelqu'un d'être à la fois un specialperson et un utilisateur simultanément. Ou, tout autre de 4 combinaisons possibles (catégorie a + b, classe b + c, classe a + c ou a + b + c). Si cette classe est stockée en tant que valeur dans un champ type et s'effondre donc ces combinaisons, et cet effondrement est inacceptable, alors je pense qu'une table secondaire serait nécessaire permettant une relation un à plusieurs. J'ai appris que vous ne jugez pas cela jusqu'à ce que vous évaluiez l'utilisation et le coût de la perte de vos informations de combinaison.

L'autre facteur qui me fait pencher vers une seule table est votre description du scénario. User est la seule entité avec un nom d'utilisateur (disons varchar (30)) et un mot de passe (disons varchar (32)). Si les champs communs sont possibles la longueur est en moyenne de 20 caractères par 20 champs, alors l'augmentation de la taille de votre colonne est de 62 sur 400, soit environ 15% - Il y a 10 ans, cela aurait été plus coûteux qu'avec les systèmes SGBDR modernes, en particulier avec un type de champ comme varchar (par exemple pour MySQL) disponible.

Et, si la sécurité vous préoccupe, il pourrait être avantageux d'avoir une table un à un secondaire appelée credentials ( user_id, username, password). Cette table serait appelée dans une jointure contextuellement au moment de la connexion, mais structurellement séparée de juste "n'importe qui" dans la table principale. Et, un LEFT JOIN est disponible pour les requêtes qui pourraient vouloir considérer les "utilisateurs enregistrés".

Ma principale considération pendant des années est toujours de considérer la signification de l'objet (et donc l'évolution possible) en dehors de la DB et dans le monde réel. Dans ce cas, tous les types de personnes ont des cœurs qui battent (j'espère), et peuvent aussi avoir des relations hiérarchiques les unes avec les autres; donc, dans le fond de mon esprit, même si ce n'est pas maintenant, nous pouvons avoir besoin de stocker de telles relations par une autre méthode. Ce n'est pas explicitement lié à votre question ici, mais c'est un autre exemple de l'expression de la relation d'un objet. Et maintenant (7 ans plus tard), vous devriez avoir un bon aperçu de la façon dont votre décision a fonctionné de toute façon:)

1
répondu Oliver Williams 2015-02-28 11:14:56

Dans le passé, je l'ai fait exactement comme vous le suggérez - avoir une table Person pour les choses communes, puis SpecialPerson lié pour la classe dérivée. Cependant, je repense à cela, car Linq2Sql veut avoir un champ dans la même table pour indiquer la différence. Je n'ai pas trop regardé le modèle d'entité, cependant-à peu près sûr que cela permet l'autre méthode.

0
répondu Danimal 2008-09-05 12:21:56

Personnellement, je stockerais toutes ces différentes classes d'utilisateurs dans une seule table. Vous pouvez alors soit avoir un champ qui stocke une valeur 'Type', ou vous pouvez impliquer quel type de personne vous avez affaire à quels champs sont remplis. Par exemple, si UserID est NULL, cet enregistrement n'est pas un utilisateur.

Vous pouvez lier à d'autres tables en utilisant un type de jointure un à un ou aucun, mais dans chaque requête, vous ajouterez des jointures supplémentaires.

La première méthode est également prise en charge par LINQ-to-SQL si vous décidez de suivre cette route (ils l'appellent 'Table par hiérarchie' ou 'TPH').

-1
répondu Chris Roberts 2008-09-05 12:20:39