Domanda

Come si fa a cambiare il sottotipo di una riga in NHibernate? Ad esempio, se ho un'entità cliente e una sottoclasse di TierOneCustomer, ho un caso in cui devo cambiare un cliente in un TierOneCustomer ma TierOneCustomer dovrebbe avere lo stesso ID (PK) dell'entità cliente originale.

La mappatura è simile alla seguente:

<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>

Sto usando il modello di gerarchia di una tabella per classe, quindi usando plain-sql, sarebbe solo una questione di un aggiornamento sql del discriminatore (CustomerType) e impostare le colonne appropriate rilevanti per il tipo. Non riesco a trovare la soluzione in NHibernate, quindi apprezzerei qualsiasi suggerimento.

Sto anche pensando se il modello è corretto considerando questo caso d'uso, ma prima di proseguire su questa strada, voglio assicurarmi che fare come descritto sopra sia effettivamente possibile in primo luogo. In caso contrario, quasi sicuramente penserò a cambiare il modello.

È stato utile?

Soluzione

La risposta breve è sì, è possibile modificare il valore del discriminatore per le righe particolari utilizzando SQL nativo .

Tuttavia, non credo che NHibernate sia destinato a funzionare in questo modo, poiché il discriminatore è generalmente " invisibile " al livello Java, dove il suo valore dovrebbe essere impostato inizialmente in base alla classe dell'oggetto persistente e mai modificato.

Consiglio di cercare un approccio più pulito. Dal punto di vista del modello a oggetti, stai cercando di convertire un oggetto superclasse in uno dei suoi tipi di sottoclasse senza cambiare l'identità della sua istanza persistente, ed è lì che si trova il conflitto (l'oggetto convertito non dovrebbe essere la stessa cosa). Due approcci alternativi sono:

  • Crea una nuova istanza di TierOneCustomer in base alle informazioni nell'oggetto Cliente originale, quindi elimina l'oggetto originale. Se per il recupero facevi affidamento sulla chiave primaria del cliente, dovrai prendere nota del nuovo PK.

o

  • Cambia il tuo approccio in modo che il tipo di oggetto (discriminatore) non debba cambiare. Invece di fare affidamento su una sottoclasse per distinguere TierOneCustomer dal cliente, è possibile utilizzare una proprietà che è possibile modificare liberamente in qualsiasi momento, ad esempio Customer.Tier = 1.

Ecco alcune discussioni correlate sui forum di Hibernate che potrebbero essere di interesse:

  1. Possiamo aggiornare la colonna del discriminatore in Hibernate
  2. Problema tabella per classe: discriminatore e proprietà
  3. Conversione di un'istanza persistente in una sottoclasse

Altri suggerimenti

Stai facendo qualcosa di sbagliato.

Quello che stai cercando di fare è cambiare il tipo di un oggetto. Non puoi farlo in .NET o in Java. Semplicemente non ha senso. Un oggetto è esattamente di un tipo concreto e il suo tipo concreto non può essere modificato dal momento in cui l'oggetto viene creato fino al momento in cui l'oggetto viene distrutto (nonostante la magia nera). Per realizzare ciò che stai cercando di fare, ma con la gerarchia di classi che hai definito, dovresti distruggere l'oggetto cliente che vuoi trasformare in un oggetto cliente di livello uno, creare un nuovo oggetto cliente di livello uno, e copiare tutte le proprietà pertinenti dall'oggetto cliente nell'oggetto cliente di primo livello. È così che lo fai con gli oggetti, in linguaggi orientati agli oggetti, con la tua gerarchia di classi.

Ovviamente, la gerarchia di classi che hai non funziona per te. Non distruggi i clienti nella vita reale quando diventano clienti di primo livello! Quindi non farlo neanche con gli oggetti. Invece, crea una gerarchia di classi che abbia senso, dati gli scenari che devi implementare. Gli scenari di utilizzo includono:

  • Un cliente che in precedenza non era di livello uno ora diventa stato di livello uno.

Ciò significa che è necessaria una gerarchia di classi in grado di acquisire con precisione questo scenario. Come suggerimento, dovresti privilegiare la composizione rispetto all'eredità. Ciò significa che potrebbe essere un'idea migliore avere una proprietà denominata IsTierOne o una proprietà denominata DiscountStrategy, ecc., A seconda di ciò che funziona meglio.

L'intero scopo di NHibernate (e Hibernate per Java) è rendere invisibile il database. Per consentire di lavorare con gli oggetti in modo nativo, con il database magicamente lì dietro le quinte per rendere persistenti gli oggetti. NHibernate ti consentirà di lavorare in modo nativo con il database, ma non è questo il tipo di scenario per cui è stato creato NHibernate.

Questo è DAVVERO in ritardo, ma potrebbe essere utile per la prossima persona che cerca di fare qualcosa di simile:

Mentre le altre risposte sono corrette sul fatto che non dovresti cambiare il discriminatore nella maggior parte dei casi, puoi farlo esclusivamente nell'ambito di NH (nessun SQL nativo) , con un uso intelligente delle proprietà mappate. Ecco il senso di ciò che usa 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());
      ...
   }
}

Il risultato finale è che il valore del discriminatore viene specificato per gli inserti e utilizzato per determinare il tipo istanziato al momento del recupero, ma se viene salvato un record di un sottotipo diverso con lo stesso ID (come se il record fosse clonato o un -bound dall'IU a un nuovo tipo), il valore del discriminatore viene aggiornato sul record esistente con quell'ID come proprietà dell'oggetto, in modo che i futuri recuperi di quel tipo siano come nuovo oggetto. Il setter è richiesto sulle proprietà perché AFAIK NHibernate non può dire che una proprietà è di sola lettura (e quindi & Quot; sola scrittura & Quot; al DB); nel mondo di NHibernate, se scrivi qualcosa nel DB, perché non lo rivuoi indietro?

Recentemente ho usato questo modello per consentire agli utenti di cambiare il tipo base di un " tour " ;, che in realtà è un insieme di regole che regolano la programmazione del " tour; " (un unico " digitale; visita " alle apparecchiature in loco di un cliente per assicurarti che tutto funzioni correttamente). Mentre sono tutti & Quot; orari dei tour & Quot; e devono essere collezionabili in liste / code ecc. in quanto tali, i diversi tipi di pianificazioni richiedono dati molto diversi e un'elaborazione molto diversa, richiedendo una struttura di dati simile a quella dell'OP. Comprendo quindi completamente il desiderio dell'OP di trattare un TierOneCustomer in modo sostanzialmente diverso, riducendo al minimo l'effetto a livello di dati, quindi, ecco qui.

Se lo stai facendo offline (ad es. in uno script di aggiornamento del DB), usa SQL e assicurati la coerenza da solo.

Se questo è qualcosa che prevedi accadrà mentre l'app è in esecuzione, penso che i tuoi requisiti siano sbagliati, proprio come mantenere lo stesso indirizzo puntatore per un oggetto diverso.

Se si salva l'ID e lo si utilizza per accedere nuovamente al cliente (ad es. in un URL), prendere in considerazione la possibilità di creare un nuovo campo che contenga un token, che sarà la chiave aziendale. Poiché non è l'ID, è facile creare una nuova istanza dell'entità e copiarla sul token (probabilmente dovrai rimuovere il token da quello precedente).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top