Pregunta

Tengo un problema con los proxies no inicializados en nhibernate

El modelo de dominio

Digamos que tengo dos jerarquías de clases paralelas: Animal, Dog, Cat y AnimalOwner, DogOwner, CatOwner, donde Dog y Cat heredan de Animal y DogOwner y CatOwner heredan de AnimalOwner. AnimalOwner tiene una referencia de tipo Animal llamada OwnedAnimal.

Aquí están las clases en el ejemplo:

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
}

Las clases tienen un mapeo nhibernate adecuado, todas las propiedades son persistentes y todo lo que se puede cargar de forma diferida se carga de forma diferida.

La lógica empresarial de la aplicación solo le permite configurar un perro en un DogOwner y un gato en un CatOwner.

El problema

Tengo un código como este:

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

Este método puede ser llamado por muchos métodos diferentes, en la mayoría de los casos el perro ya está en la memoria y todo está bien, pero rara vez el perro todavía no está en la memoria, en este caso obtengo un "inhibidor no inicializado nhibernate". pero el elenco lanza una excepción porque nhibernate genera un proxy para Animal y no para Perro.

Entiendo que así es como funciona nhibernate, pero necesito saber el tipo sin cargar el objeto, o más correctamente, necesito que el proxy no inicializado sea un proxy de Cat o Dog y no un proxy de Animal.

Restricciones

  • No puedo cambiar el modelo de dominio, el modelo me lo entrega otro departamento, intenté que cambiaran el modelo y fallé.
  • El modelo real es mucho más complicado que el ejemplo y las clases tienen muchas referencias entre ellas, por lo que el uso de la carga ansiosa o la adición de combinaciones a las consultas está fuera de discusión por razones de rendimiento.
  • Tengo control total del código fuente, el mapeo hbm y el esquema de la base de datos y puedo cambiarlos de la forma que quiera (siempre y cuando no cambie las relaciones entre las clases de modelos).
  • Tengo muchos métodos como el del ejemplo y no quiero modificarlos todos.

Gracias,
Nir

¿Fue útil?

Solución

Es más fácil desactivar la carga diferida para la clase de animales. Dices que está mayormente en la memoria de todos modos.

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

Como una variante de eso, también puede usar no-proxy , vea esta publicación :

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

Hasta donde puedo ver, solo funciona cuando el AnimalOwner es realmente un proxy.

OR

Puede usar genéricos en el dueño del animal para hacer de la referencia una clase concreta.

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

class CatOwner : AnimalOwner<Cat>
{
}

class DogOwner : AnimalOwner<Dog>
{
}

OR

Puede asignar los DogOwners y CatOwners en tablas separadas, y definir el tipo de animal concreto en el mapeo.

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

OR

Usted juega un poco con NHibernate, como se propone en este blog . NH realmente puede devolver el objeto real detrás del proxy. Aquí una implementación un poco más simple como se propone allí:

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

que se puede usar así:

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

Otros consejos

Creo que recientemente tuvimos un problema similar, la solución AFAIR fue darle a 'Animal' un 'método / propiedad': '

public Animal Self { get { return this; } }

Esto podría ser lanzado para corregir "animal". Lo que sucede es que su objeto original tiene una referencia al objeto proxy nhibernate (cuando está cargado perezosamente), que actúa como Animal para todos los métodos expuestos a través de la clase Animal (pasa todas las llamadas al objeto cargado). Sin embargo, no puede ser lanzado como cualquiera de sus otros animales porque no es ninguno de estos, solo emula la clase Animal. Sin embargo, la clase que está encapsulada por AnimalProxy se puede convertir como animal subclasificado porque es una instancia real de la clase correcta, solo necesita acceder a su this referencia.

Puede intentar poner este método en su entidad base:

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

Es posible que desee probar esto para ver el tipo de proxy (suponiendo NH 2.0+):

((INHibernateProxy)proxy).HibernateLazyInitializer.PersistentClass

Pero este tipo de casting o "tipo leerlo" es muy mala práctica de todos modos ...

Si hemos estado trabajando con el mismo problema, el problema es que el proxy generado es el proxy de Animal en lugar de Perro.

La solución que utilizamos fue volver a cargar el objeto:

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

Esto vuelve a su sesión y vuelve a cargar el objeto con el tipo correcto.

Espero que esto ayude

Si usa NHibernate fluido, puede usar una anulación de mapeo automático para desactivar la carga diferida solo para esa propiedad:

public class DogOwnerMapOverride : IAutoMappingOverride<DogOwner>
{
    public void Override( AutoMapping<DogOwner> mapping )
    {
        mapping.References( x => x.OwnedAnimal ).Not.LazyLoad();
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top