Question

J'ai un problème de proxy non initialisé dans nhibernate

Le modèle de domaine

Supposons que j'ai deux hiérarchies de classes parallèles: Animal, Dog, CatOwner et AnimalOwner, DogOwner, CatOwner, où Dog et Cat héritent à la fois d'Animal et de DogOwner et de CatOwner, hérités d'AnimalOwner. AnimalOwner a une référence de type Animal appelée OwnedAnimal.

Voici les classes de l'exemple:

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
}

Les classes ont le mappage nhibernate approprié, toutes les propriétés sont persistantes et tout ce qui peut être chargé paresseux est chargé paresseux.

La logique métier de l'application vous permet uniquement de définir un chien dans un DogOwner et un chat dans un CatOwner.

Le problème

J'ai un code comme celui-ci:

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

Cette méthode peut être appelée de différentes manières. Dans la plupart des cas, le chien est déjà en mémoire et tout va bien, mais rarement, le chien n’est pas déjà en mémoire. Dans ce cas, je reçois un "proxy non initialisé" nhibernate. mais la distribution lève une exception car nhibernate génère un proxy pour Animal et non pour Dog.

Je comprends que c’est ainsi que fonctionne nhibernate, mais j’ai besoin de connaître le type sans charger l’objet - ou plus exactement, j’ai besoin que le proxy non initialisé soit un proxy de Cat ou Dog et non un proxy d’Animal.

Contraintes

  • Je ne peux pas changer le modèle de domaine, le modèle m’a été remis par un autre service. J'ai essayé de le faire changer de modèle et j’ai échoué.
  • Le modèle réel est beaucoup plus compliqué que l'exemple et les classes ont de nombreuses références entre elles. Il est hors de question de charger ou d'ajouter des jointures aux requêtes pour des raisons de performances.
  • J'ai le contrôle total du code source, du mappage hbm et du schéma de base de données et je peux les modifier à ma guise (tant que je ne modifie pas les relations entre les classes de modèle).
  • J'ai plusieurs méthodes comme celle de l'exemple et je ne veux pas toutes les modifier.

Merci,
Nir

Était-ce utile?

La solution

Il est plus facile de désactiver le chargement paresseux pour la classe des animaux. De toute façon, vous dites que c'est surtout en mémoire.

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

En guise de variante, vous pouvez également utiliser no-proxy , voir cet article :

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

Autant que je sache, cela ne fonctionne que lorsque AnimalOwner est en réalité un proxy.

OU

Vous pouvez utiliser des génériques sur le propriétaire de l'animal pour transformer la référence en classe concrète.

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

class CatOwner : AnimalOwner<Cat>
{
}

class DogOwner : AnimalOwner<Dog>
{
}

OU

Vous pouvez mapper les codes DogOwners et CatOwners dans des tables distinctes et définir le type d'animal concret dans le mappage.

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

OU

Vous déconnez un peu avec NHibernate, comme proposé dans ceci blog . NH est effectivement capable de retourner l'objet réel derrière le proxy. Voici une implémentation un peu plus simple telle que proposée:

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

qui peut être utilisé comme ceci:

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

Autres conseils

Je pense que nous avons récemment eu un problème similaire, la solution AFAIR était de donner à "Animal" une auto-"méthode / propriété":

public Animal Self { get { return this; } }

Ceci pourrait alors être utilisé pour corriger "animal". Ce qui se passe, c'est que votre objet d'origine a une référence à l'objet proxy nhibernate (lorsqu'il est chargé paresseusement), qui agit en tant qu'Animal pour toutes les méthodes exposées via la classe Animal (il transmet tous les appels à l'objet chargé). Cependant, il ne peut pas être présenté comme l'un de vos autres animaux car ce n'est pas le cas, il n'imule que la classe Animal. Cependant, la classe encapsulée par AnimalProxy peut être convertie en animal sous-classé, car il s’agit d’une instance réelle de la classe correcte. Il vous suffit de vous rendre à cette référence.

Vous pouvez essayer de mettre cette méthode sur votre entité de base:

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

Vous voudrez peut-être essayer ceci pour voir le type traité par proxy (en supposant que NH 2.0 +):

((INHibernateProxy)proxy).HibernateLazyInitializer.PersistentClass

Mais ce type de casting ou de "type furtivement" est très mauvaise pratique quand même ...

Si nous travaillons avec le même problème, le problème est que le proxy généré est le proxy de Animal plutôt que de Dog.

La solution que nous avons utilisée consistait à recharger l'objet:

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

Ceci retourne à votre session et recharge l'objet avec le type correct.

J'espère que cela vous aidera

Si vous utilisez Fluent NHibernate, vous pouvez utiliser un remplacement par mappage automatique pour désactiver le chargement différé pour cette propriété uniquement:

public class DogOwnerMapOverride : IAutoMappingOverride<DogOwner>
{
    public void Override( AutoMapping<DogOwner> mapping )
    {
        mapping.References( x => x.OwnedAnimal ).Not.LazyLoad();
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top