Ottenere proxy del tipo corretto in NHibernate
-
03-07-2019 - |
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
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();
}
}