Pergunta

Eu tenho um proxy para uma entidade preguiçosa que foi criado na sessão carregando uma entidade filho. Uma subsequente buscar na entidade-mãe apenas retorna o proxy NH. Eu preciso da instância real para verificar o tipo (a entidade juntou subclasses). Eu devo estar faltando alguma coisa, mas eu não consigo encontrar uma maneira de fazer isso. não Session.Refresh (proxy) não parecem ajuda, nem faz qualquer sabor de HQL que eu tentei.

alguém pode ajudar?

Foi útil?

Solução

Para forçar um proxy de ser obtida a partir do banco de dados, você pode usar o método NHibernateUtil.Initialize(proxy), ou acesso um método / propriedade do proxy.

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

Para verificar se um objeto é inicializado ou não, você pode usar o método NHibernateUtil.IsInitialized(proxy).

Update:

Para remover um objeto do cache de sessão, use o método Session.Evict(obj).

session.Evict(myEntity);

Informações sobre Evict e outros métodos para gerenciar o cache de sessão pode ser encontrado em capítulo 14,5 dos docs NHibernate.

Outras dicas

Na minha opinião, em vez de resolver esse problema, você deve sim repensar o seu design. Você está absolutamente certo, que você não pode usar polimorfismo nesta situação - seja diretamente fazer entidade responsável pela operação que você está tentando realizar ou padrão de uso visitante. Me deparei com esta questão algumas vezes e sempre decidiu projetar a mudança - que resultou em código mais claro. Eu sugiro que você faça o mesmo, a menos que você está absolutamente certo de que depender de tipo é a melhor solução.

O problema

Para se ter exemplo com pelo menos alguma semelhança com o mundo real, vamos supor que você tem seguintes entidades:

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

Ele tinha, naturalmente, uma pequena parte do muito maior modelo. E agora você está enfrentando um problema: para cada tipo concreto de operação, há uma maneira diferente para exibi-lo:

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

Simples, métodos sobrecarregados vai trabalhar no caso simples:

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

Infelizmente, métodos sobrecarregados estão vinculados em tempo de compilação, portanto, assim que você introduzir um array / lista / whatever de operações, apenas uma genérica (operação Operação) sobrecarga será chamado.

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

Existem duas soluções para este problema, e ambos têm desvantagens. Você pode introduzir um método abstrato / virtual na operação para imprimir informações de fluxo selecionado. Mas isso vai misturar preocupações UI em seu modelo, de modo que não é aceitável para você (eu vou lhe mostrar como você pode melhorar esta solução para atender às suas expectativas em um momento).

Você também pode criar um monte de ifs na forma de:

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

Esta solução é feio e propenso a erros. Toda vez que você adicionar a mudança / / Tipo de remover de operação, você tem que passar por todos os lugares que você usou estes corte e modificá-lo. E se você perder um único lugar, você provavelmente só será capaz de pegar o tempo de execução -. Há rigorosas verificações de tempo de compilação para alguns dos erros (como a falta de um subtipo)

Além disso, esta solução irá falhar assim que você introduzir qualquer tipo de proxy.

Como funciona a procuração

O código abaixo é de proxy muito simples (nesta implementação é igual ao padrão Decorator -. Mas esses padrões não são os mesmos em geral Levaria algum código adicional para distinguir esses dois padrões).

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

Como você pode ver - há apenas uma classe proxy para hierarquia todo. Por quê? Porque você deve escrever o seu código de uma maneira que não depende de tipo concreto - apenas na abstração fornecido. Este proxy pode adiar o carregamento entidade no tempo - talvez você não vai usá-lo em tudo? Talvez você vai usar apenas 2 de 1000 entidades? Por que carga de todos eles, então?

Assim NHibernate usa procuração como no acima (muito mais sofisticado, embora) para adiar o carregamento entidade. Ele poderia criar uma procuração por sub-tipo, mas isso iria destruir propósito inteiro de carregamento lento. Se você olhar carefuly em como NHibernate armazena subclasses você vai ver, que, a fim de determinar o que entidade tipo é, você tem que carregá-lo. Por isso, é impossível ter proxies concretos -. Você só pode ter o mais abstrato, OperationProxy

Altough a solução com ifs é feio - era uma solução. Agora, quando você introduziu proxies para o seu problema - ele não é mais trabalhar. De modo que apenas nos deixa com o método polimórfico, o que é inaceitável por causa da mistura responsabilidade UI para o seu modelo. Vamos corrigir isso.

inversão de dependência e visitante padrão

Primeiro, vamos dar uma olhada em como a solução com métodos virtuais seria semelhante (código acabou de adicionar):

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 agora, quando você chama:

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

tudo funciona como um encanto.

Para remover essa dependência UI no modelo, vamos criar uma interface:

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

modelo Modificar Vamos depender de esta interface:

E agora criar uma implementação - ConsoleOutputOperationVisitor (Eu apaguei métodos 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);
    }
}

O que acontece aqui? Quando você chamar Aceitar na operação de umnd passar um visitante, a implementação de aceitar será chamado, onde sobrecarga apropriada do método de Visita será invocado (compilador pode determinar o tipo de "isto"). Então você combina o "poder" de métodos virtuais e sobrecargas para se método apropriado chamado. Como você pode ver -. Agora UI referência aqui, modelo só depende de uma interface, que pode ser incluído na camada do modelo

Agora, para começar este trabalho, uma implementação da 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
}

E código:

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

Estou bem ciente de que esta não é uma solução perfeita. Você ainda vai ter que modificar a interface e os visitantes como você adicionar novos tipos. Mas você começa a verificação de tempo de compilação e nunca vai perder nada. Uma coisa que seria muito difícil de alcançar com este método é obter subtipos conectáveis ??- mas eu não estou convencido de que este é um cenário válido de qualquer maneira. Você também terá de modificar esse padrão para atender às suas necessidades no cenário concreto, mas vou deixar isso para você.

Desactivar o carregamento lento irá forçar o exemplo real a ser devolvido em vez do proxy NHibernate.

por exemplo ..

mapping.Not.LazyLoad ();

ou

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

Uma vez que o proxy é derivado da classe de entidade, você provavelmente só pode verificar entity.GetType (). BaseType para obter o seu tipo definido.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top