Domanda

Ho un problema con i proxy non inizializzati in nhibernate

Il modello di dominio

Diciamo che ho due gerarchie di classi parallele: Animal, Dog, Cat e AnimalOwner, DogOwner, CatOwner dove Dog e Cat ereditano entrambi da Animal e DogOwner e CatOwner ereditano entrambi da AnimalOwner. AnimalOwner ha un riferimento di tipo Animal chiamato OwnedAnimal.

Ecco le classi nell'esempio:

public abstract class Animal
{
   // some properties
}

public class Dog : Animal
{
   // some more properties
}

public class Cat : Animal
{
   // some more properties
}

public class AnimalOwner 
{
   public virtual Animal OwnedAnimal {get;set;}
   // more properties...
}

public class DogOwner : AnimalOwner
{
   // even more properties
}

public class CatOwner : AnimalOwner
{
   // even more properties
}

Le classi hanno una mappatura appropriata, tutte le proprietà sono persistenti e tutto ciò che può essere caricato in modo pigro è caricato in modo lento.

La logica aziendale dell'applicazione consente solo di impostare un cane in un DogOwner e un gatto in un CatOwner.

Il problema

Ho un codice come questo:

public void ProcessDogOwner(DogOwner owner)
{
   Dog dog = (Dog)owner.OwnedAnimal;
   ....
}

Questo metodo può essere chiamato da molti metodi diversi, nella maggior parte dei casi il cane è già in memoria e tutto è ok, ma raramente il cane non è già in memoria - in questo caso ottengo un proxy negligente "non inizializzato"; ma il cast genera un'eccezione perché nhibernate genera un proxy per Animal e non per Dog.

Capisco che è così che funziona la proibizione, ma ho bisogno di conoscere il tipo senza caricare l'oggetto - o, più correttamente, ho bisogno che il proxy non inizializzato sia un proxy di Cat o Dog e non un proxy di Animal.

Vincoli

  • Non riesco a cambiare il modello di dominio, il modello mi viene consegnato da un altro reparto, ho provato a convincerli a cambiare il modello e non ci sono riuscito.
  • Il modello attuale è molto più complicato rispetto all'esempio e le classi hanno molti riferimenti tra di loro, l'utilizzo del caricamento desideroso o l'aggiunta di join alle query è fuori discussione per motivi di prestazioni.
  • Ho il pieno controllo del codice sorgente, della mappatura hbm e dello schema del database e posso cambiarli come voglio (purché non cambi le relazioni tra le classi del modello).
  • Ho molti metodi come quello nell'esempio e non voglio modificarli tutti.

Grazie,
Nir

È stato utile?

Soluzione

È più semplice disattivare il caricamento lento per la classe animale. Dici comunque che è principalmente nella memoria.

<class name="Animal" lazy="false">
<!-- ... -->
</class>

Come variante, puoi anche usare no-proxy , vedi questo post :

<property name="OwnedAnimal" lazy="no-proxy"/>

Per quanto posso vedere, funziona solo quando AnimalOwner è in realtà un proxy.

o

Puoi usare i generici sul proprietario dell'animale per rendere il riferimento una classe concreta.

class AnimalOwner<TAnimal>
{
  virtual TAnimal OwnedAnimal {get;set;}
}

class CatOwner : AnimalOwner<Cat>
{
}

class DogOwner : AnimalOwner<Dog>
{
}

o

Puoi mappare DogOwners e CatOwners in tabelle separate e definire il tipo di animale concreto nella mappatura.

<class name="CatOwner">
  <!-- ... -->
  <property name="OwnedAninal" class="Cat"/>
</class>
<class name="DogOwner">
  <!-- ... -->
  <property name="OwnedAninal" class="Dog"/>
</class>

o

Fai un casino con NHibernate, come proposto in questo blog . NH è effettivamente in grado di restituire l'oggetto reale dietro il proxy. Qui un'implementazione un po 'più semplice come proposta lì:

    public static T CastEntity<T>(this object entity) where T: class
    {
        var proxy = entity as INHibernateProxy;
        if (proxy != null)
        {
            return proxy.HibernateLazyInitializer.GetImplementation() as T;
        }
        else
        {
            return entity as T;
        }
    }

che può essere utilizzato in questo modo:

Dog dog = dogOwner.OwnedAnimal.CastEntit<Dog>();

Altri suggerimenti

Penso che di recente abbiamo avuto un problema simile, la soluzione AFAIR consisteva nel dare a "Animal" un metodo "self / property":

public Animal Self { get { return this; } }

Questo potrebbe quindi essere lanciato per correggere "animal". Quello che succede è che l'oggetto originale ha un riferimento a oggetto proxy negligente (quando è caricato pigramente), che funge da animale per tutti i metodi esposti tramite la classe animale (passa tutte le chiamate all'oggetto caricato). Tuttavia non può essere lanciato come uno qualsiasi degli altri tuoi animali perché non è nessuno di questi, emula solo la classe Animale. Tuttavia, la classe incapsulata da AnimalProxy può essere lanciata come animale di sottoclasse perché è una vera istanza di classe corretta, devi solo arrivare al suo questo riferimento.

Puoi provare a mettere questo metodo sulla tua entità di base:

public virtual T As<T>() where T : Entity {
      return this as T;
}

Potresti provare questo per vedere il tipo proxy (supponendo NH 2.0+):

((INHibernateProxy)proxy).HibernateLazyInitializer.PersistentClass

Ma questo tipo di casting o "tipo sbirciare" è comunque una pessima pratica ...

Se stiamo lavorando con lo stesso problema, il problema è che il proxy generato è il proxy di Animal piuttosto che di Dog.

La soluzione che abbiamo usato è stata quella di ricaricare l'oggetto:

Dog dog = this.CurrentSession.Load<Dog>(owner.OwnedAnimal.AnimalID);

Torna alla sessione e ricarica l'oggetto con il tipo corretto.

Spero che questo aiuti

Se si utilizza Fluid NHibernate, è possibile utilizzare un override di auto-mapping per disattivare il caricamento lento solo per quella proprietà:

public class DogOwnerMapOverride : IAutoMappingOverride<DogOwner>
{
    public void Override( AutoMapping<DogOwner> mapping )
    {
        mapping.References( x => x.OwnedAnimal ).Not.LazyLoad();
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top