Question

J'ai un proxy pour une entité paresseuse qui a été créé dans la session en chargeant une entité enfant. Une extraction ultérieure sur l'entité mère ne renvoie que le proxy NH. J'ai besoin de l'instance réelle pour vérifier le type (l'entité a joint des sous-classes). Il me manque quelque chose, mais je ne trouve pas le moyen de le faire. Session.Refresh (proxy) ne semble pas aider, ni aucune version de HQL que j'ai essayée.

Quelqu'un peut-il aider?

Était-ce utile?

La solution

Pour forcer l'extraction d'un proxy de la base de données, vous pouvez utiliser la méthode NHibernateUtil.Initialize (proxy) ou accéder à une méthode / propriété du proxy.

var foo = session.Get<Foo>(id);
NHibernateUtil.Initialize(foo.Bar);

Pour vérifier si un objet est initialisé ou non, vous pouvez utiliser la méthode NHibernateUtil.IsInitialized (proxy) .

Mise à jour:

Pour supprimer un objet du cache de session, utilisez la méthode Session.Evict (obj) .

session.Evict(myEntity);

Vous trouverez des informations sur Expulsion et d'autres méthodes de gestion du cache de session dans chapitre 14.5 des documents NHibernate.

Autres conseils

À mon avis, plutôt que de résoudre ce problème, vous devriez plutôt repenser votre conception. Êtes-vous absolument certain que vous ne pouvez pas utiliser le polymorphisme dans cette situation - responsabilisez directement l'entité pour l'opération que vous essayez d'exécuter ou utilisez le modèle de visiteur. Je suis tombé sur ce problème à plusieurs reprises et j'ai toujours décidé de changer de conception. Je vous suggère de faire la même chose, à moins que vous ne soyez absolument sûr que le type soit la meilleure solution.

Le problème

Pour avoir un exemple ressemblant au moins au monde réel, supposons que vous ayez les entités suivantes:

public abstract class Operation
{
    public virtual DateTime PerformedOn { get; set; }
    public virtual double Ammount { get; set; }
}

public class OutgoingTransfer : Operation
{
    public virtual string TargetAccount { get; set; }
}

public class AtmWithdrawal : Operation
{
    public virtual string AtmAddress { get; set; }
}

Ce serait naturellement une petite partie d'un modèle beaucoup plus grand. Et maintenant vous faites face à un problème: pour chaque type d'opération concret, il existe une manière différente de l'afficher:

private static void PrintOperation(Operation operation)
{
    Console.WriteLine("{0} - {1}", operation.PerformedOn,
                      operation.Ammount);
}

private static void PrintOperation(OutgoingTransfer operation)
{
    Console.WriteLine("{0}: {1}, target account: {2}",
                      operation.PerformedOn, operation.Ammount,
                      operation.TargetAccount);
}

private static void PrintOperation(AtmWithdrawal operation)
{
    Console.WriteLine("{0}: {1}, atm's address: {2}",
                      operation.PerformedOn, operation.Ammount,
                      operation.AtmAddress);
}

Les méthodes simples surchargées fonctionnent dans le cas simple:

var transfer = new OutgoingTransfer
               {
                   Ammount = -1000,
                   PerformedOn = DateTime.Now.Date,
                   TargetAccount = "123123123"
               };

var withdrawal = new AtmWithdrawal
                 {
                     Ammount = -1000,
                     PerformedOn = DateTime.Now.Date,
                     AtmAddress = "Some address"
                 };

// works as intended
PrintOperation(transfer);
PrintOperation(withdrawal);

Malheureusement, les méthodes surchargées sont liées au moment de la compilation. Ainsi, dès que vous introduisez un tableau / une liste d'opérations, peu importe les opérations, seule une surcharge générique (opération) sera appelée.

Operation[] operations = { transfer, withdrawal };
foreach (var operation in operations)
{
    PrintOperation(operation);
}

Il existe deux solutions à ce problème et les deux présentent des inconvénients. Vous pouvez introduire une méthode abstraite / virtuelle dans Operation pour imprimer des informations dans le flux sélectionné. Mais cela va mélanger les préoccupations d’UI dans votre modèle, donc ce n’est pas acceptable pour vous (je vais vous montrer comment améliorer cette solution pour répondre à vos attentes dans un instant).

Vous pouvez également créer de nombreux ifs sous la forme:

if(operation is (ConcreteType))
   PrintOperation((ConcreteType)operation);

Cette solution est laide et source d’erreurs. Chaque fois que vous ajoutez / modifiez / supprimez un type d'opération, vous devez passer par chaque endroit où vous avez utilisé ces informations et le modifier. Et si vous manquez un endroit, vous ne pourrez probablement que capturer ce temps d'exécution - aucune vérification stricte au moment de la compilation pour détecter certaines erreurs (comme l'absence d'un sous-type).

En outre, cette solution échouera dès que vous introduirez un type de proxy.

Fonctionnement du proxy

Le code ci-dessous est un proxy TRÈS simple (dans cette implémentation, c'est le même motif décoratif - mais ces motifs ne sont généralement pas les mêmes. Il faudrait du code supplémentaire pour distinguer ces deux motifs).

public class OperationProxy : Operation
{
    private readonly Operation m_innerOperation;

    public OperationProxy(Operation innerOperation)
    {
        if (innerOperation == null)
            throw new ArgumentNullException("innerOperation");
        m_innerOperation = innerOperation;
    }


    public override double Ammount
    {
        get { return m_innerOperation.Ammount; }
        set { m_innerOperation.Ammount = value; }
    }

    public override DateTime PerformedOn
    {
        get { return m_innerOperation.PerformedOn; }
        set { m_innerOperation.PerformedOn = value; }
    }
}

Comme vous pouvez le constater, il n’existe qu’une seule classe de proxy pour l’ensemble de la hiérarchie. Pourquoi? Parce que vous devriez écrire votre code d'une manière qui ne dépend pas du type concret - seulement de l'abstraction fournie. Ce proxy peut différer le chargement des entités dans le temps - peut-être que vous ne l'utiliserez pas du tout? Peut-être utiliserez-vous seulement 2 entités sur 1000? Pourquoi les charger tous alors?

Donc, NHibernate utilise un proxy comme ci-dessus (bien que beaucoup plus sophistiqué, cependant) pour différer le chargement des entités. Cela pourrait créer 1 proxy par sous-type, mais cela détruirait tout le but du chargement paresseux. Si vous regardez attentivement comment NHibernate stocke les sous-classes que vous verrez, vous devez la charger pour déterminer le type d'entité. Il est donc impossible d’avoir des procurations concrètes: vous ne pouvez avoir que le plus abstrait, OperationProxy.

Bien que la solution avec ifs soit moche - c’était une solution. Maintenant, lorsque vous avez introduit des proxies à votre problème, cela ne fonctionne plus. Cela nous laisse donc avec une méthode polymorphe, ce qui est inacceptable en raison du mélange de la responsabilité de l’assurance-chômage avec votre modèle. Réparons cela.

Inversion de dépendance et modèle de visiteur

Tout d'abord, examinons comment la solution utilisant des méthodes virtuelles pourrait ressembler (code ajouté):

public abstract class Operation
{
    public abstract void PrintInformation();
}

public class OutgoingTransfer : Operation
{
    public override void PrintInformation()
    {
        Console.WriteLine("{0}: {1}, target account: {2}",
                      PerformedOn, Ammount, TargetAccount);
    }
}

public class AtmWithdrawal : Operation
{
    public override void PrintInformation()
    {
        Console.WriteLine("{0}: {1}, atm's address: {2}",
                          PerformedOn, Ammount, AtmAddress);
    }
}

public class OperationProxy : Operation
{
    public override void PrintInformation()
    {
        m_innerOperation.PrintInformation();
    }
}

Et maintenant, quand vous appelez:

Operation[] operations = { transfer, withdrawal, proxy };
foreach (var operation in operations)
{
    operation.PrintInformation();
}

tout fonctionne comme un charme.

Afin de supprimer cette dépendance de l'interface utilisateur dans le modèle, créons une interface:

public interface IOperationVisitor
{
    void Visit(AtmWithdrawal operation);
    void Visit(OutgoingTransfer operation);
}

Modifions le modèle en fonction de cette interface:

Et créez maintenant une implémentation - ConsoleOutputOperationVisitor (j'ai supprimé les méthodes PrintInformation):

public abstract class Operation
{
    public abstract void Accept(IOperationVisitor visitor);
}

public class OutgoingTransfer : Operation
{
    public override void Accept(IOperationVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class AtmWithdrawal : Operation
{
    public override void Accept(IOperationVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class OperationProxy : Operation
{
    public override void Accept(IOperationVisitor visitor)
    {
        m_innerOperation.Accept(visitor);
    }
}

Que se passe-t-il ici? Lorsque vous appelez Accepter en opération et transmettez un visiteur, l'implémentation d'accepter sera appelée, où la surcharge de la méthode Visit sera invoquée (le compilateur peut déterminer le type de "ceci"). Donc, vous combinez " power " des méthodes virtuelles et des surcharges pour obtenir la méthode appropriée appelée. Comme vous pouvez le constater - à présent, référence de l'interface utilisateur ici, le modèle dépend uniquement d'une interface, qui peut être incluse dans la couche de modèle.

Alors maintenant, pour que cela fonctionne, une implémentation de l'interface:

 public class ConsoleOutputOperationVisitor : IOperationVisitor
 {
    #region IOperationVisitor Members
    public void Visit(AtmWithdrawal operation)
    {
        Console.WriteLine("{0}: {1}, atm's address: {2}",
                          operation.PerformedOn, operation.Ammount,
                          operation.AtmAddress);
    }

    public void Visit(OutgoingTransfer operation)
    {
        Console.WriteLine("{0}: {1}, target account: {2}",
                          operation.PerformedOn, operation.Ammount,
                          operation.TargetAccount);
    }

    #endregion
}

Et code:

Operation[] operations = { transfer, withdrawal, proxy };
foreach (var operation in operations)
{
    operation.Accept(visitor);
}

Je suis bien conscient que ce n'est pas une solution parfaite. Vous devrez toujours modifier l'interface et les visiteurs à mesure que vous ajoutez de nouveaux types. Mais vous obtenez une vérification du temps de compilation et vous ne manquerez de rien. Une chose qui serait vraiment difficile à réaliser avec cette méthode est d’obtenir des sous-types enfichables - mais je ne suis pas convaincu que ce soit un scénario valable de toute façon. Vous devrez également modifier ce modèle pour répondre à vos besoins dans un scénario concret, mais je vous laisse cela.

Si vous désactivez le chargement différé, l'instance réelle sera renvoyée à la place du proxy NHibernate.

par exemple ..

mapping.Not.LazyLoad ();

ou

<class name="OrderLine" table="OrderLine" lazy="false" >

Puisque le proxy est dérivé de la classe d'entité, vous pouvez probablement simplement vérifier entity.GetType (). BaseType pour obtenir votre type défini.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top