nhibernate class-hierarchy

NHibernate-Cambio de subtipos



class-hierarchy (4)

¿Cómo se hace para cambiar el subtipo de una fila en NHibernate? Por ejemplo, si tengo una entidad Cliente y una subclase de TierOneCustomer, tengo un caso en el que necesito cambiar un Cliente a un Cliente TierOne, pero el Cliente TierOne debe tener el mismo Id (PK) que la entidad Cliente original.

El mapeo se ve así:

<class name="Customer" table="SiteCustomer" discriminator-value="C"> <id name="Id" column="Id" type="Int64"> <generator class="identity" /> </id> <discriminator column="CustomerType" /> ... properties snipped ... <subclass name="TierOneCustomer" discriminator-value="P"> ... more properties ... </subclass> </class>

Estoy usando el modelo de jerarquía de una tabla por clase, así que usar sql simple, sería solo una cuestión de una actualización sql del discriminador (CustomerType) y establecer las columnas apropiadas relevantes para el tipo. No puedo encontrar la solución en NHibernate, por lo que agradecería cualquier sugerencia.

También estoy pensando si el modelo es correcto teniendo en cuenta este caso de uso, pero antes de ir por esa ruta, quiero asegurarme de que hacer lo que se describe arriba es posible en primer lugar. Si no, seguramente pensaré en cambiar el modelo.


Estas haciendo algo mal.

Lo que intenta hacer es cambiar el tipo de un objeto. No puedes hacer eso en .NET o en Java. Eso simplemente no tiene sentido. Un objeto es exactamente de un tipo concreto, y su tipo concreto no se puede cambiar desde el momento en que se crea el objeto hasta el momento en que se destruye el objeto (a pesar de la magia negra). Para lograr lo que está intentando hacer, pero con la jerarquía de clases que estableció, tendría que destruir el objeto del cliente que desea convertir en un objeto de cliente de nivel uno, crear un nuevo objeto de cliente de nivel uno, y copie todas las propiedades relevantes del objeto del cliente al objeto del cliente de nivel uno. Así es como lo haces con objetos, en lenguajes orientados a objetos, con tu jerarquía de clases.

Obviamente, la jerarquía de clases que tienes no está funcionando para ti. ¡No destruyas clientes en la vida real cuando se conviertan en clientes de nivel uno! Entonces no lo hagas con los objetos tampoco. En cambio, proponga una jerarquía de clases que tenga sentido, dados los escenarios que necesita implementar. Sus escenarios de uso incluyen:

  • Un cliente que anteriormente no es de nivel uno ahora se convierte en estado de nivel uno.

Eso significa que necesita una jerarquía de clases que pueda capturar con precisión este escenario. Como sugerencia, debes favorecer la composición sobre la herencia. Eso significa que puede ser una mejor idea tener una propiedad llamada IsTierOne , o una propiedad llamada DiscountStrategy , etc., según lo que funcione mejor.

El propósito total de NHibernate (e Hibernate para Java) es hacer que la base de datos sea invisible. Para permitirle trabajar con objetos de forma nativa, con la base de datos mágicamente detrás de las escenas para que sus objetos sean persistentes. NHibernate le permitirá trabajar con la base de datos de forma nativa, pero ese no es el tipo de escenario para el que NHibernate está diseñado.


La respuesta corta es sí, puede cambiar el valor del discriminador para la (s) fila (s) particular (es) usando SQL nativo .

Sin embargo, no creo que NHibernate tenga la intención de funcionar de esta manera, ya que el discriminador generalmente es "invisible" para la capa de Java, donde se supone que su valor debe establecerse inicialmente según la clase del objeto persistente y nunca cambiará.

Recomiendo buscar un enfoque más limpio. Desde el punto de vista del modelo de objetos, intenta convertir un objeto superclase en uno de sus tipos de subclase sin cambiar la identidad de su instancia persistente, y ahí es donde está el conflicto (el objeto convertido realmente no se supone que sea la misma cosa). Dos enfoques alternativos son:

  • Cree una nueva instancia de TierOneCustomer basada en la información en el objeto Customer original, luego elimine el objeto original. Si confiaba en la clave principal del cliente para su recuperación, deberá tomar nota del nuevo PK.

o

  • Cambie su enfoque para que el tipo de objeto (discriminador) no necesite cambiar. En lugar de confiar en una subclase para distinguir a TierOneCustomer del Cliente, puede usar una propiedad que puede modificar libremente en cualquier momento, es decir, Customer.Tier = 1.

Aquí hay algunas discusiones relacionadas con los foros de Hibernate que pueden ser de su interés:

  1. ¿Podemos actualizar la columna del discriminador en Hibernate?
  2. Problema de tabla por clase: discriminador y propiedad
  3. Convertir una instancia persistente en una subclase

Si lo está haciendo fuera de línea (por ejemplo, en un script de actualización de base de datos), simplemente use SQL y asegúrese de ser coherente usted mismo.

Si esto es algo que planea que ocurra mientras la aplicación se está ejecutando, creo que sus requisitos son incorrectos, al igual que mantener la misma dirección del puntero para un objeto diferente es incorrecto.

Si guarda la ID y la usa para acceder nuevamente al cliente (por ejemplo, en una URL), considere crear un nuevo campo que contenga un token para esto que será la clave comercial. Como no es la ID, es fácil crear una nueva instancia de entidad y copiarla sobre el token (es probable que deba eliminar el token del anterior).


Esto es REALMENTE tarde, pero puede ser útil para la próxima persona que busque hacer algo similar:

Si bien las otras respuestas son correctas , no debe cambiar el discriminador en la mayoría de los casos, puede hacerlo simplemente dentro del alcance de NH (sin SQL nativo), con algún uso inteligente de las propiedades mapeadas. Aquí está la esencia de usar FluentNH:

public enum CustomerType //not sure it''s needed { Customer, TierOneCustomer } public class Customer { //You should be able to use the Type name instead, //but I know this enum-based approach works public virtual CustomerType Type { get {return CustomerType.Customer;} set {} //small code smell; setter exists, no error, but it doesn''t do anything. } ... } public class TierOneCustomer:Customer { public override CustomerType Type {get {return CustomerType.TierOneCustomer;} set{}} ... } public class CustomerMap:ClassMap<Customer> { public CustomerMap() { ... DiscriminateSubClassesOnColumn<string>("CustomerType"); DiscriminatorValue(CustomerType.Customer.ToString()); //here''s the magic; make the discriminator updatable //"Not.Insert()" is required to prevent the discriminator column //showing up twice in an insert statement Map(x => x.Type).Column("CustomerType").Update().Not.Insert(); } } public class TierOneCustomerMap:SubclassMap<TierOneCustomer> { public CustomerMap() { //same idea, different discriminator value ... DiscriminatorValue(CustomerType.TierOneCustomer.ToString()); ... } }

El resultado final es que el valor del discriminador se especifica para inserciones y se usa para determinar el tipo de instancia en la recuperación, pero luego si se guarda un registro de un subtipo diferente con el mismo Id (como si el registro se clonara o no desde la IU a un nuevo tipo), el valor del discriminador se actualiza en el registro existente con ese ID como propiedad del objeto, de modo que las recuperaciones futuras de ese tipo sean como el nuevo objeto. El colocador es requerido en las propiedades porque a AFAIK NHibernate no se le puede decir que una propiedad es de solo lectura (y por lo tanto "solo escritura" en el DB); en el mundo de NHibernate, si escribes algo en la base de datos, ¿por qué no querrías recuperarlo?

Utilicé este patrón recientemente para permitir a los usuarios cambiar el tipo básico de un "recorrido", que en realidad es un conjunto de reglas que rigen la programación del "recorrido" real (una única "visita" digital al equipo en el sitio de un cliente) para asegurarse de que todo funcione correctamente). Si bien son todos "programas de visitas" y deben ser recopilables en listas / colas, etc., los diferentes tipos de programas requieren datos muy diferentes y un procesamiento muy diferente, que requieren una estructura de datos similar a la del OP. Por lo tanto, entiendo completamente el deseo del OP de tratar a un Cliente de TierOne de una manera sustancialmente diferente mientras se minimiza el efecto en la capa de datos, así que, aquí está.