Pregunta

Tengo un proxy para una entidad perezosa que se ha creado en la sesión al cargar una entidad secundaria. Una recuperación posterior en la entidad principal solo devuelve el proxy NH. Necesito la instancia real para verificar el tipo (la entidad ha unido subclases). Debo estar perdiendo algo, pero no puedo encontrar una manera de hacer esto. Session.Refresh (proxy) no parece ayudar, ni tampoco ningún tipo de HQL que haya intentado.

¿Alguien puede ayudar?

¿Fue útil?

Solución

Para forzar que se busque un proxy de la base de datos, puede usar el método NHibernateUtil.Initialize (proxy) , o acceder a un método / propiedad del proxy.

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

Para verificar si un objeto está inicializado o no, puede usar el método NHibernateUtil.IsInitialized (proxy) .

Actualización :

Para eliminar un objeto de la caché de sesión, use el método Session.Evict (obj) .

session.Evict(myEntity);

La información sobre Evict y otros métodos para administrar la caché de sesión se pueden encontrar en capítulo 14.5 de los documentos de NHibernate.

Otros consejos

En mi opinión, en lugar de resolver este problema, deberías repensar tu diseño. ¿Está absolutamente seguro de que no puede usar el polimorfismo en esta situación? Ya sea que haga que la entidad sea directamente responsable de la operación que está intentando realizar o utilice el patrón de visitante. Encontré este problema varias veces y siempre decidí cambiar el diseño, lo que resultó en un código más claro. Le sugiero que haga lo mismo, a menos que esté absolutamente seguro de que confiar en el tipo es la mejor solución.

El problema

Para tener un ejemplo con al menos algo parecido al mundo real, supongamos que tienes las siguientes 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; }
}

Naturalmente, sería una pequeña parte de un modelo mucho más grande. Y ahora está enfrentando un problema: para cada tipo concreto de operación, hay una forma diferente de mostrarlo:

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

Los métodos simples y sobrecargados funcionarán en un caso 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);

Lamentablemente, los métodos sobrecargados se enlazan en el momento de la compilación, por lo que tan pronto como introduzca una matriz / lista / cualquiera de las operaciones, solo se llamará una sobrecarga genérica (operación Operación).

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

Hay dos soluciones para este problema, y ??ambas tienen desventajas. Puede introducir un método abstracto / virtual en Operación para imprimir información en la secuencia seleccionada. Pero esto combinará las preocupaciones de UI con su modelo, por lo que no es aceptable para usted (le mostraré cómo puede mejorar esta solución para satisfacer sus expectativas en un momento).

También puedes crear muchos ifs en forma de:

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

Esta solución es fea y propensa a errores. Cada vez que agregue / cambie / elimine el tipo de operación, tendrá que recorrer cada lugar donde usó estos trucos y modificarlo. Y si pierde un lugar, es probable que solo pueda capturar ese tiempo de ejecución, sin estrictos controles de compilación para algunos errores (como la falta de un subtipo).

Además, esta solución fallará tan pronto como introduzcas cualquier tipo de proxy.

Cómo funciona el proxy

El código a continuación es un proxy MUY simple (en esta implementación es igual que el patrón decorador, pero esos patrones no son los mismos en general. Se necesitaría un código adicional para distinguir esos dos patrones).

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 puede ver, solo hay una clase de proxy para toda la jerarquía. ¿Por qué? Porque debe escribir su código de una manera que no dependa de un tipo concreto, solo de la abstracción proporcionada. Este proxy podría diferir la carga de la entidad a tiempo, ¿tal vez no la usará en absoluto? ¿Tal vez usará solo 2 de 1000 entidades? ¿Por qué cargarlos todos entonces?

Entonces, NHibernate usa un proxy como el de arriba (aunque mucho más sofisticado, sin embargo) para diferir la carga de la entidad. Podría crear 1 proxy por subtipo, pero destruiría todo el propósito de la carga perezosa. Si observa detenidamente cómo NHibernate almacena las subclases que verá, para determinar qué tipo de entidad es, debe cargarla. Por lo tanto, es imposible tener proxies concretos: solo puede tener el más abstracto, OperationProxy.

A pesar de la solución, si es feo, fue una solución. Ahora, cuando introdujo proxies en su problema, ya no funciona. Eso nos deja con el método polimórfico, que es inaceptable debido a que la responsabilidad de la interfaz de usuario se mezcla con la de su modelo. Vamos a arreglar eso.

Inversión de dependencia y patrón de visitante

Primero, veamos cómo se vería la solución con métodos virtuales (código recién agregado):

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

Y ahora, cuando llames:

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

todo funciona como un encanto.

Para eliminar esta dependencia de IU en el modelo, vamos a crear una interfaz:

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

Modifiquemos el modelo para que dependa de esta interfaz:

Y ahora cree una implementación - ConsoleOutputOperationVisitor (He eliminado los métodos de información de impresión):

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

¿Qué pasa aquí? Cuando llame a Aceptar en operación y pase a un visitante, se llamará la implementación de aceptar, donde se invocará la sobrecarga apropiada del método de Visita (el compilador puede determinar el tipo de " esto "). Así que combinas " poder " de métodos virtuales y sobrecargas para obtener el método apropiado llamado. Como puede ver, ahora la referencia de la interfaz de usuario aquí, el modelo solo depende de una interfaz, que se puede incluir en la capa del modelo.

Así que ahora, para que esto funcione, una implementación de la interfaz:

 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
}

Y código:

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

Soy consciente de que esta no es una solución perfecta. Aún tendrá que modificar la interfaz y los visitantes a medida que agrega nuevos tipos. Pero obtienes tiempo de compilación y nunca te perderás nada. Una cosa que sería realmente difícil de lograr con este método es obtener subtipos conectables, pero no estoy convencido de que este sea un escenario válido de todos modos. También tendrás que modificar este patrón para satisfacer tus necesidades en un escenario concreto, pero te lo dejo a ti.

La desactivación de la carga perezosa obligará a que se devuelva la instancia real en lugar del proxy NHibernate.

por ejemplo ..

mapping.Not.LazyLoad ();

o

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

Dado que el proxy se deriva de la clase de entidad, probablemente solo puedas verificar entity.GetType (). BaseType para obtener tu tipo definido.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top