Domanda

Ho un proxy per un'entità pigra che è stata creata nella sessione caricando un'entità figlio. Un successivo recupero sull'entità padre restituisce solo il proxy NH. Ho bisogno dell'istanza effettiva per verificare il tipo (l'entità ha aderito alle sottoclassi). Mi manca qualcosa, ma non riesco a trovare un modo per farlo. Session.Refresh (proxy) non sembra aiutare, né alcun sapore di HQL che ho provato.

Qualcuno può aiutare?

È stato utile?

Soluzione

Per forzare il recupero di un proxy dal database, è possibile utilizzare il metodo NHibernateUtil.Initialize (proxy) o accedere a un metodo / proprietà del proxy.

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

Per verificare se un oggetto è inizializzato o meno, è possibile utilizzare il metodo NHibernateUtil.IsInitialized (proxy) .

Aggiornamento:

Per rimuovere un oggetto dalla cache di sessione, utilizzare il metodo Session.Evict (obj) .

session.Evict(myEntity);

Informazioni su Evict e altri metodi per gestire la cache di sessione sono disponibili in capitolo 14.5 dei documenti di NHibernate.

Altri suggerimenti

Secondo me, piuttosto che risolvere questo problema, dovresti piuttosto ripensare il tuo progetto. Sei assolutamente sicuro di non poter usare il polimorfismo in questa situazione - o rendere direttamente l'entità responsabile dell'operazione che si sta tentando di eseguire o utilizzare il modello di visitatore. Mi sono imbattuto in questo problema alcune volte e ho sempre deciso di cambiare il design: il risultato è stato un codice più chiaro. Ti suggerisco di fare lo stesso, a meno che tu non sia assolutamente sicuro che fare affidamento sul tipo sia la soluzione migliore.

Il problema

Per avere un esempio con almeno qualche somiglianza con il mondo reale, supponiamo che tu abbia le seguenti entità:

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

Sarebbe naturalmente una piccola parte di un modello molto più grande. E ora stai affrontando un problema: per ogni tipo concreto di Operazione, c'è un modo diverso di visualizzarlo:

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

I metodi semplici e sovraccarichi funzioneranno nel caso semplice:

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

Sfortunatamente, i metodi sovraccarichi sono associati al momento della compilazione, quindi non appena si introduce un array / elenco / qualunque delle operazioni, verrà chiamato solo un sovraccarico generico (Operazione).

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

Esistono due soluzioni a questo problema ed entrambi hanno degli aspetti negativi. È possibile introdurre un metodo astratto / virtuale in Operazione per stampare informazioni sullo stream selezionato. Ma questo mescolerà le preoccupazioni dell'interfaccia utente nel tuo modello, quindi non è accettabile per te (ti mostrerò come puoi migliorare questa soluzione per soddisfare le tue aspettative in un momento).

Puoi anche creare molti if sotto forma di:

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

Questa soluzione è brutta e soggetta a errori. Ogni volta che aggiungi / modifichi / rimuovi il tipo di operazione, devi andare in ogni luogo in cui hai usato questi hack e modificarli. E se ti manca un posto, probabilmente sarai in grado di catturare quel runtime - nessun controllo rigoroso in fase di compilazione per alcuni errori (come perdere un sottotipo).

Inoltre, questa soluzione fallirà non appena si introduce qualsiasi tipo di proxy.

Come funziona il proxy

Il codice seguente è un proxy MOLTO semplice (in questa implementazione è lo stesso del modello decoratore - ma quei modelli non sono gli stessi in generale. Ci vorrebbe del codice aggiuntivo per distinguere quei due modelli).

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

Come puoi vedere, esiste solo una classe proxy per l'intera gerarchia. Perché? Perché dovresti scrivere il tuo codice in un modo che non dipenda dal tipo concreto - solo dall'astrazione fornita. Questo proxy potrebbe ritardare il caricamento dell'entità in tempo - forse non lo userai affatto? Forse utilizzerai solo 2 entità su 1000? Perché caricarli tutti allora?

Quindi NHibernate utilizza proxy come sopra (molto più sofisticato, tuttavia) per rinviare il caricamento delle entità. Potrebbe creare 1 proxy per sottotipo, ma distruggerebbe l'intero scopo del caricamento lento. Se osservi attentamente come NHibernate archivia le sottoclassi, vedrai che, per determinare di che tipo di entità è, devi caricarlo. Quindi è impossibile avere proxy concreti: puoi avere solo il più astratto, OperationProxy.

Anche se la soluzione con se è brutta - era una soluzione. Ora, quando hai introdotto i proxy per il tuo problema, non funziona più. Questo ci lascia solo con il metodo polimorfico, il che è inaccettabile a causa della miscelazione della responsabilità dell'interfaccia utente con il tuo modello. Risolviamolo.

Inversione di dipendenza e modello di visitatori

Innanzitutto, diamo un'occhiata a come sarebbe la soluzione con metodi virtuali (appena aggiunto il codice):

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

E ora, quando chiami:

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

tutto funziona come un incantesimo.

Per rimuovere questa dipendenza dell'interfaccia utente nel modello, creiamo un'interfaccia:

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

Modifichiamo il modello in base a questa interfaccia:

E ora crea un'implementazione - ConsoleOutputOperationVisitor (ho eliminato i metodi 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);
    }
}

Cosa succede qui? Quando si chiama Accept su operazione e si passa un visitatore, verrà chiamata l'implementazione di accept, dove verrà invocato il sovraccarico appropriato del metodo Visit (il compilatore può determinare il tipo di "questo"). Quindi unisci " potenza " di metodi virtuali e sovraccarichi per ottenere il metodo appropriato chiamato. Come puoi vedere - ora il riferimento all'interfaccia utente qui, il modello dipende solo da un'interfaccia, che può essere inclusa nel livello del modello.

Quindi ora, per farlo funzionare, un'implementazione dell'interfaccia:

 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
}

E codice:

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

Sono ben consapevole che questa non è una soluzione perfetta. Dovrai comunque modificare l'interfaccia e i visitatori quando aggiungi nuovi tipi. Ma ottieni il controllo del tempo di compilazione e non ti perderai mai nulla. Una cosa che sarebbe davvero difficile da ottenere usando questo metodo è ottenere sottotipi collegabili - ma non sono convinto che questo sia uno scenario valido comunque. Dovrai anche modificare questo modello per soddisfare le tue esigenze in uno scenario concreto, ma lo lascerò a te.

La disabilitazione del caricamento lento imporrà la restituzione dell'istanza effettiva anziché il proxy NHibernate.

ad es ..

mapping.Not.LazyLoad ();

o

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

Poiché il proxy è derivato dalla classe entità, probabilmente puoi semplicemente controllare entity.GetType (). BaseType per ottenere il tipo definito.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top