2009-01-25 14 views
5

¿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.NHibernate - Cambio de subtipos

El mapeo es como la siguiente:

<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 sola tabla por clase, por lo que el uso normal-SQL, que sería sólo una cuestión de una actualización de SQL de la discriminator (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 seguir por esa ruta, quiero asegurarme de que hacer lo que se describe arriba es realmente posible en primer lugar. Si no, seguramente pensaré en cambiar el modelo.

Respuesta

9

La respuesta breve es sí, puede cambiar el valor del discriminador para las filas en particular usando native SQL.

Sin embargo, no creo que NHibernate esté diseñado para funcionar de esta manera, ya que el discriminador es generalmente "invisible" a la capa Java, donde se supone que su valor se establece inicialmente según la clase del objeto persistente y nunca cambió.

Recomiendo buscar en 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 Cliente 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

  • Cambiar la aproximación por lo que el tipo de objeto (discriminador) no tiene por qué cambiar. En lugar de depender de una subclase de distinguir TierOneCustomer del cliente, se puede utilizar una propiedad que se puede modificar libremente en cualquier momento, es decir Customer.Tier = 1.

Estas son algunas discusiones relacionadas en los foros de Hibernate que pueden ser de su interés:

  1. Can we update the discriminator column in Hibernate
  2. Table-per-Class Problem: Discriminator and Property
  3. Converting a persisted instance into a subclass
+0

Sí, creo que voy a refactorizar la jerarquía y, probablemente, optar por el enfoque de propiedad simple. Gran respuesta, gracias. –

1

Si lo está haciendo sin conexión (p. en una secuencia de comandos de actualización de base de datos), simplemente use SQL y asegure la coherencia 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 la posibilidad de 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).

5

Estás haciendo algo mal.

Lo que estás tratando de 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 tiene no le funciona. ¡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 un estado 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., dependiendo de qué funcione mejor.

Todo el propósito 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.

+0

Me gustó esta respuesta, pero me pareció que la de David estaba un poco mejor así que acepté la suya. Aún así, muchas gracias por la buena respuesta, que he votado. –

2

Esto es realmente tarde, pero puede ser de utilidad a la siguiente persona que quiera hacer algo similar:

Mientras que las otras respuestas son correctas que usted no debe cambio el discriminador en la mayoría de los casos, puede hacerlo exclusivamente dentro del alcance de NH (sin SQL nativo), con algún uso inteligente de propiedades mapeadas. Aquí está la esencia de la misma usando 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 se especifica el valor del discriminador para las inserciones, y se utiliza para determinar el tipo instanciado en la recuperación, pero a continuación, si un registro de un subtipo diferente con el mismo Id se guarda (como si el registro se hubiera clonado o desvinculado de la UI 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 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 "recorrido", que en realidad es un conjunto de reglas que rigen la programación de la "visita" real (una única "visita" digital a un cliente equipo en el sitio para garantizar que todo funcione correctamente). Si bien todos son "itinerarios turísticos" y deben ser recopilables en listas/colas, etc., los diferentes tipos de programaciones requieren datos muy diferentes y un procesamiento muy diferente, lo que exige 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á.

Cuestiones relacionadas