¿Cómo lidiar con el polimorfismo en una base de datos?


Ejemplo

Tengo Person, SpecialPerson, y User. Person y SpecialPerson son solo personas - no tienen un nombre de usuario o contraseña en un sitio, pero se almacenan en una base de datos para el mantenimiento de registros. El usuario tiene todos los mismos datos que Person y potencialmente SpecialPerson, junto con un nombre de usuario y contraseña a medida que se registran en el sitio.


¿Cómo abordaría este problema? ¿Tendría una tabla Person que almacena todos los datos comunes a una persona y usa una clave para buscar sus datos en SpecialPerson (si son una persona especial) y Usuario (si son un usuario) y viceversa?

Author: Charles Menguy, 2008-09-05

13 answers

Generalmente hay tres formas de asignar la herencia de objetos a las tablas de la base de datos.

Puede hacer una tabla grande con todos los campos de todos los objetos con un campo especial para el tipo. Esto es rápido pero desperdicia espacio, aunque las bases de datos modernas ahorran espacio al no almacenar campos vacíos. Y si solo estás buscando a todos los usuarios en la tabla, con cada tipo de persona en ella, las cosas pueden ser lentas. No todos los or-mapeadores admiten esto.

Puede hacer diferentes tablas para todos los diferentes clases secundarias con todas las tablas que contienen los campos de clase base. Esto está bien desde una perspectiva de rendimiento. Pero no desde una perspectiva de mantenimiento. Cada vez que tu clase base cambia, todas las tablas cambian.

También puede crear una tabla por clase como sugirió. De esta manera necesitas uniones para obtener todos los datos. Así que es menos eficiente. Creo que es la solución más limpia.

Lo que quieras usar depende, por supuesto, de tu situación. Ninguna de las soluciones es perfecta así que tienes que sopesar los pros y los contras.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-14 01:13:32

Echa un vistazo a Martin Fowler Patrones de Arquitectura de Aplicaciones Empresariales:

  • Herencia de tabla única :

    Al asignar a una base de datos relacional, tratamos de minimizar las uniones que se pueden montar rápidamente al procesar una estructura de herencia en varias tablas. Herencia de tabla única mapea todos los campos de todas las clases de una estructura de herencia en una sola tabla.

  • Tabla de Clases Herencia :

    Desea estructuras de base de datos que se asignen claramente a los objetos y permitan enlaces en cualquier parte de la estructura de herencia. La herencia de tablas de clases admite esto mediante el uso de una tabla de base de datos por clase en la estructura de herencia.

  • Herencia de la Tabla de concreto :

    Pensando en las tablas desde el punto de vista de una instancia de objeto, una ruta sensata es tomar cada objeto en memoria y asignarlo a una sola fila de la base de datos. Este implica Herencia de Tabla concreta, donde hay una tabla para cada clase concreta en la jerarquía de herencia.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-14 01:28:32

Si el Usuario, la Persona y la persona especial tienen las mismas claves foráneas, entonces tendría una sola tabla. Agregue una columna llamada Tipo que está restringida a ser Usuario, Persona o Persona Especial. A continuación, en función del valor de tipo tienen restricciones en las otras columnas opcionales.

Para el código objeto no hace mucha diferencia si tiene las tablas separadas o varias tablas para representar el polimorfismo. Sin embargo, si tiene que hacer SQL contra la base de datos, es mucho más fácil si el el polimorfismo se captura en una sola tabla...siempre que las claves foráneas para los subtipos sean las mismas.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-05 12:25:15

Lo que voy a decir aquí va a enviar a los arquitectos de bases de datos a conniptions, pero aquí va:

Considere una vista de base de datos como el equivalente de una definición de interfaz. Y una tabla es el equivalente de una clase.

Así que en su ejemplo, todas las clases de 3 personas implementarán la interfaz IPerson. Así que tienes 3 tablas-una para cada uno de 'Usuario', 'Persona'y' Persona especial'.

Luego tenga una vista 'PersonView' o lo que sea que seleccione las propiedades comunes (como definido por su 'interfaz') de las 3 tablas en la vista única. Utilice una columna 'PersonType' en esta vista para almacenar el tipo real de la persona que se almacena.

Así que cuando estás ejecutando una consulta que puede ser operada en cualquier tipo de persona, solo consulta la vista 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-05 12:29:20

Esto podría no ser lo que la OPERACIÓN quería preguntar, pero pensé que podría lanzar esto aquí.

Recientemente tuve un caso único de polimorfismo db en un proyecto. Teníamos entre 60 y 120 clases posibles, cada una con su propio conjunto de 30 a 40 atributos únicos, y entre 10 y 12 atributos comunes en todas las clases . Decidimos ir por la ruta SQL-XML y terminamos con una sola tabla. Algo como :

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

Que contiene todas las propiedades comunes como columnas y luego una gran propiedad XML bolsa. La capa OR era entonces responsable de leer / escribir las propiedades respectivas de las propiedades de xmlother. Un poco como :

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

(terminamos mapeando la columna xml como un documento Hastable en lugar de XML, pero puede usar lo que mejor se adapte a su DAL)

No va a ganar ningún premio de diseño, pero funcionará si tienes un gran número (o desconocido) de posibles clases. Y en SQL2005 todavía puede usar XPATH en sus consultas SQL para seleccionar filas basadas en alguna propiedad que se almacena como XML.. es solo una pequeña penalización por rendimiento.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-05 13:19:38

Hay tres estrategias básicas para manejar la herencia en una base de datos relacional, y una serie de alternativas más complejas/personalizadas dependiendo de sus necesidades exactas.

  • Tabla por jerarquía de clases. Una tabla para toda la jerarquía.
  • Tabla por subclase. Se crea una tabla separada para cada subclase con una asociación 0-1 entre las tablas subclases.
  • Tabla por clase de hormigón. Se crea una sola mesa para cada clase de concreto.

Cada uno de estos appoaches plantea sus propios problemas sobre la normalización, el código de acceso a los datos y el almacenamiento de datos, aunque mi preferencia personal es usar tabla por subclase a menos que haya un rendimiento específico o una razón estructural para optar por una de las alternativas.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-05 12:38:34

A riesgo de ser un 'astronauta de la arquitectura' aquí, estaría más inclinado a ir con tablas separadas para las subclases. Haga que la clave principal de las tablas de subclase también sea una clave foránea que se vincule al supertipo.

La razón principal para hacerlo de esta manera es que luego se vuelve mucho más lógicamente consistente y no termina con muchos campos que son NULOS y sin sentido para ese registro en particular. Este método también hace que sea mucho más fácil agregar campos adicionales a los subtipos a medida que itera el proceso de diseño.

Esto agrega la desventaja de agregar uniones a sus consultas, lo que puede afectar el rendimiento, pero casi siempre opto por un diseño ideal primero, y luego busco optimizar más tarde si resulta necesario. Las pocas veces que he ido por el camino' óptimo ' primero casi siempre me he arrepentido más tarde.

Así que mi diseño sería algo como

PERSONA (personid, nombre, dirección, teléfono ...)

PERSONA ESPECIAL (personid REFERENCIAS PERSONA (personid), campos adicionales...)

USUARIO (personid HACE REFERENCIA A PERSON(personid), nombre de usuario, encryptedpassword, campos adicionales...)

También puede crear vistas más adelante que agreguen el supertipo y el subtipo, si es necesario.

El único defecto en este enfoque es si se encuentra buscando intensamente los subtipos asociados con un supertipo particular. No hay una respuesta fácil a esto en la parte superior de mi cabeza, podría rastrearlo programáticamente si es necesario, o bien ejecutar consultas globales soem y almacenar en caché los resultados. Realmente dependerá de la aplicación.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-05 12:41:46

Yo diría que, dependiendo de lo que diferencia a la Persona y a la Persona Especial, probablemente no quieras polimorfismo para esta tarea.

Crearía una tabla User, una tabla Person que tiene un campo de clave foránea nullable para el Usuario (es decir, la Persona puede ser un Usuario, pero no tiene que hacerlo).
Entonces haría una tabla de SpecialPerson que se relaciona con la tabla de la persona con cualquier campo adicional en ella. Si un registro está presente en SpecialPerson para un determinado Person.ID, es una persona especial.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-05 12:26:51

En nuestra empresa nos ocupamos del polimorfismo mediante la combinación de todos los campos en una tabla y su peor integridad referencial y no se puede imponer y modelo muy difícil de entender. Yo recomendaría contra ese enfoque con seguridad.

Iría con Tabla por subclase y también evitaría el golpe de rendimiento, pero usando OR donde podemos evitar unirse con todas las tablas de subclase construyendo consultas sobre la marcha basándonos en el tipo. La estrategia antes mencionada funciona para un solo nivel de registro, pero para la actualización masiva o seleccionar no se puede evitar.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-17 23:06:54

Sí, también consideraría un TypeID junto con una tabla PersonType si es posible que haya más tipos. Sin embargo, si solo hay 3 que no debería ser 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-05 12:23:59

Este es un post anterior, pero pensé que voy a opinar desde un punto de vista conceptual, procesal y de rendimiento.

La primera pregunta que me haría es la relación entre persona, persona especial y usuario, y si es posible que alguien sea tanto una persona especial y un usuario simultáneamente. O, de cualquier otro de 4 combinaciones posibles (clase a + b, clase b + c, a + c, o a + b + c). Si esta clase se almacena como un valor en un campo type y, por lo tanto, se colapsaría estas combinaciones, y ese colapso es inaceptable, entonces yo pensaría que se requeriría una tabla secundaria que permitiera una relación de uno a muchos. He aprendido que no juzgas eso hasta que evalúas el uso y el costo de perder la información de tu combinación.

El otro factor que me hace inclinarme hacia una sola tabla es su descripción del escenario. User es la única entidad con un nombre de usuario(say varchar (30)) y una contraseña (say varchar(32)). Si los campos comunes son posibles la longitud es un promedio de 20 caracteres por cada 20 campos, entonces su aumento de tamaño de columna es de 62 sobre 400, o alrededor del 15% - hace 10 años esto habría sido más costoso que con los sistemas RDBMS modernos, especialmente con un tipo de campo como varchar (por ejemplo, para MySQL) disponible.

Y, si la seguridad le preocupa, podría ser ventajoso tener una tabla secundaria de uno a uno llamada credentials ( user_id, username, password). Esta tabla se invocaría en una COMBINACIÓN contextualmente en el momento de inicio de sesión, pero estructuralmente separada de solo "cualquiera" en la mesa principal. Y, un LEFT JOIN está disponible para consultas que podrían querer considerar "usuarios registrados".

Mi principal consideración durante años sigue siendo considerar el significado del objeto (y por lo tanto su posible evolución) fuera del DB y en el mundo real. En este caso, todos los tipos de personas tienen corazones latentes (espero), y también pueden tener relaciones jerárquicas entre sí; por lo tanto, en el fondo de mi mente, incluso si no ahora, es posible que necesitemos almacenar tales relaciones al otro método. Eso no está explícitamente relacionado con tu pregunta aquí, pero es otro ejemplo de la expresión de la relación de un objeto. Y por ahora (7 años más tarde) usted debe tener una buena idea de cómo su decisión funcionó de todos modos:)

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-28 11:14:56

En el pasado lo he hecho exactamente como sugieres have tener una tabla de personas para cosas comunes, luego una persona especial vinculada para la clase derivada. Sin embargo, estoy repensando que, como Linq2Sql quiere tener un campo en la misma tabla indica la diferencia. No he mirado el modelo de entidad demasiado, aunque pretty bastante seguro de que permite el otro método.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-05 12:21:56

Personalmente, almacenaría todas estas clases de usuario diferentes en una sola tabla. A continuación, puede tener un campo que almacena un valor de 'Tipo', o puede implicar con qué tipo de persona está tratando por qué campos se rellenan. Por ejemplo, si userId es NULL, entonces este registro no es un Usuario.

Puede enlazar a otras tablas usando un tipo de unión de uno a uno o ninguno, pero luego en cada consulta agregará uniones adicionales.

El primer método también es compatible con LINQ-to-SQL si decide ir por esa ruta (lo llaman 'Tabla Por jerarquía' o '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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-05 12:20:39