Pregunta

Ejemplo

Tengo Person, SpecialPerson, y User. Person y SpecialPerson son solo personas: no tienen un nombre de usuario ni una contraseña en un sitio, pero están almacenados en una base de datos para mantener registros.El usuario tiene todos los mismos datos que Person y potencialmente SpecialPerson, junto con un nombre de usuario y contraseña tal como están registrados en el sitio.


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

¿Fue útil?

Solución

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

Puedes 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 busca todos los usuarios en la tabla, con cada tipo de persona en ella, las cosas pueden volverse lentas.No todos los or-mappers admiten esto.

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

También puedes hacer una tabla por clase como sugeriste.De esta manera necesitas uniones para obtener todos los datos.Entonces tiene menos rendimiento.Creo que es la solución más limpia.

Lo que quieras utilizar depende, por supuesto, de tu situación.Ninguna de las soluciones es perfecta, por lo que hay que sopesar los pros y los contras.

Otros consejos

Echa un vistazo a Martin Fowler. Patrones de arquitectura de aplicaciones empresariales:

  • Herencia de tabla única:

    Al asignar a una base de datos relacional, intentamos minimizar las uniones que pueden acumularse rápidamente al procesar una estructura de herencia en varias tablas.La herencia de tabla única asigna todos los campos de todas las clases de una estructura de herencia en una sola tabla.

  • Herencia de tabla de clases:

    Quiere estructuras de bases 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 mesa de hormigón:

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

Si el Usuario, la Persona y la Persona Especial tienen las mismas claves externas, entonces tendría una sola tabla.Agregue una columna llamada Tipo que esté restringida a ser Usuario, Persona o Persona especial.Luego, según el valor de Tipo, tenga restricciones en las otras columnas opcionales.

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

Lo que voy a decir aquí va a hacer que los arquitectos de bases de datos se pongan nerviosos, pero aquí va:

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

Entonces, en su ejemplo, las clases de 3 personas implementarán la interfaz IPerson.Entonces tiene 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 (según lo 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.

Entonces, cuando esté ejecutando una consulta que pueda operarse en cualquier tipo de persona, simplemente consulte la vista PersonView.

Puede que esto no sea lo que el OP quiso preguntar, pero pensé que podría incluir 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 seguir 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 bolsa de propiedades XML.La capa ORM era entonces responsable de leer/escribir las propiedades respectivas de XMLOtherProperties.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 un documento XML, pero puedes usar lo que mejor se adapte a tu DAL)

No ganará ningún premio de diseño, pero funcionará si tiene una cantidad grande (o desconocida) de clases posibles.Y en SQL2005 aún puede usar XPATH en sus consultas SQL para seleccionar filas en función de alguna propiedad almacenada como XML.es sólo una pequeña penalización de rendimiento que hay que asimilar.

Existen tres estrategias básicas para manejar la herencia en una base de datos relacional y una serie de alternativas más complejas/personalizadas según 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 subclasificadas.
  • Tabla por clase de hormigón.Se crea una única tabla para cada clase concreta.

Cada uno de estos enfoques 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 utilizar tabla por subclase a menos que haya una razón estructural o de desempeño específica para optar por una de las alternativas.

A riesgo de ser un "astronauta de la arquitectura" aquí, me inclinaría más por utilizar tablas separadas para las subclases.Haga que la clave principal de las tablas de subclases también sea una clave externa que se vincule al supertipo.

La razón principal para hacerlo de esta manera es que luego se vuelve mucho más consistente lógicamente y no termina con muchos campos que son NULL 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 su proceso de diseño.

Esto agrega la desventaja de agregar JOIN a sus consultas, lo que puede afectar el rendimiento, pero casi siempre elijo primero un diseño ideal y luego busco optimizarlo más tarde si resulta necesario.Las pocas veces que he tomado el camino "óptimo" primero, casi siempre me he arrepentido después.

Entonces mi diseño sería algo así como

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

PERSONA ESPECIAL (idpersona PERSONA DE REFERENCIAS(idpersona), campos extra...)

USUARIO (idpersona PERSONA DE REFERENCIAS(idpersona), nombre de usuario, contraseña cifrada, campos adicionales...)

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

El único defecto de este enfoque es que se encuentra buscando intensamente los subtipos asociados con un supertipo en particular.No hay una respuesta fácil a esto que se me ocurre; puede rastrearlo mediante programación si es necesario, o ejecutar algunas consultas globales y almacenar en caché los resultados.Realmente dependerá de la aplicación.

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

Crearía una tabla de Usuario, una tabla de Persona que tiene un campo de clave externa anulable para Usuario (es decir, la Persona puede ser un Usuario, pero no es necesario).
Luego crearía una tabla SpecialPerson que se relaciona con la tabla Person con campos adicionales en ella.Si hay un registro presente en SpecialPerson para un Person.ID determinado, él/ella/eso es una persona especial.

En nuestra empresa nos ocupamos del polimorfismo combinando todos los campos en una tabla y en el peor de los casos se puede imponer ninguna integridad referencial y un modelo muy difícil de entender.Seguramente recomendaría no ese enfoque.

Yo elegiría Tabla por subclase y también evitaría un impacto en el rendimiento, pero usaría ORM donde podemos evitar unirnos con todas las tablas de subclase al crear consultas sobre la marcha según el tipo.La estrategia antes mencionada funciona para la extracción de un nivel de registro único, pero para la actualización o selección masiva no puede evitarla.

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, eso no debería ser necesario.

Esta es una publicación anterior, pero pensé en intervenir desde un punto de vista conceptual, de procedimiento y de desempeño.

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

El otro factor que me hace inclinarme por una sola tabla es su descripción del escenario. User es la única entidad con un nombre de usuario (digamos varchar(30)) y contraseña (digamos varchar(32)).Si la longitud posible de los campos comunes es un promedio de 20 caracteres por 20 campos, entonces el aumento del tamaño de la columna es 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 (p. ej.para MySQL) disponible.

Y, si le preocupa la seguridad, podría resultar ventajoso tener una tabla secundaria uno a uno llamada credentials ( user_id, username, password).Esta tabla se invocaría en un JOIN contextualmente en el momento del inicio de sesión, pero estructuralmente separada de "cualquiera" en la tabla principal.y, un LEFT JOIN Está disponible para consultas que quieran 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 de la base de datos y en el mundo real.En este caso, todo tipo de personas tienen corazones palpitantes (espero) y también pueden tener relaciones jerárquicas entre sí;entonces, en el fondo de mi mente, aunque no sea ahora, es posible que necesitemos almacenar dichas relaciones mediante otro método.Eso no está relacionado explícitamente con su pregunta aquí, pero es otro ejemplo de la expresión de la relación de un objeto.Y a estas alturas (7 años después) deberías tener una buena idea de cómo funcionó tu decisión de todos modos :)

En el pasado, lo hice exactamente como usted sugiere: tener una tabla de Persona para cosas comunes y luego una Persona Especial vinculada para la clase derivada.Sin embargo, estoy reconsiderando eso, ya que Linq2Sql quiere tener un campo en la misma tabla que indique la diferencia.Sin embargo, no he examinado demasiado el modelo de entidad; estoy bastante seguro de que permite el otro método.

Personalmente, almacenaría todas estas clases de usuarios diferentes en una sola tabla.Luego puede tener un campo que almacene un valor de 'Tipo' o puede dar a entender con qué tipo de persona está tratando según los campos que se completan.Por ejemplo, si UserID es NULL, entonces este registro no es un Usuario.

Puede vincular 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 seguir esa ruta (lo llaman 'Tabla por jerarquía' o 'TPH').

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top