2011-02-15 17 views
5

¿Cómo se puede tratar el siguiente problema?Propiedades de NHibernate con carga lenta en Equals y GetHashCode

Estamos utilizando cargado ligeramente NHibernate propiedades y siempre que estamos llamando Equals() o GetHashCode() propiedades usadas, habrá perezoso-cargado, que puede causar una cascada de operaciones de carga perezosa. La carga impaciente podría usarse como alternativa, pero creo que solo en casos específicos y no como una solución general.

Un escenario típico sería el siguiente:

public class AbstractSaveableObject { 
    [Id(0, Name = "Id", UnsavedValue = null)] 
    [Generator(1, Class = "native")] 
    public virtual long? Id { get; set; } 
} 

[Class(NameType = typeof(ClassA))] 
public class ClassA : AbstractSavableObject { 
    [Bag(0, Inverse = true, Cascade = "none")] 
    [Key(1, Column = "ClassA")] 
    [OneToMany(2, ClassType = typeof(ClassB))] 
    public virtual ICollection<ClassB> ClassBs { get; set; } 
} 

[Class(NameType = typeof(ClassB))] 
public class ClassB : AbstractSavableObject { 

    [ManyToOne(Column = "ClassA")] 
    public virtual ClassA ClassA { get; set; } 

    [ManyToOne] 
    public virtual ClassC ClassC { get; set; } 

    [ManyToOne] 
    public virtual ClassD ClassD { get; set; } 

    public virtual bool Equals(ClassB other) 
    { 
     if (ReferenceEquals(null, other)) 
     { 
      return false; 
     } 
     if (ReferenceEquals(this, other)) 
     { 
      return true; 
     } 
     return Equals(other.ClassC, ClassC) && Equals(other.ClassD, ClassD); 
    } 
} 

Implementación de GetHashCode y Equals(object) se han omitido por razones de brevedad.

¿Qué estrategias se pueden utilizar para abordar este problema?

+0

¿No tiene ninguna clave principal en sus entidades? – asgerhallas

+0

Sí, lo hago, los he omitido, en realidad todas las clases persistentes se derivan de una clase base abstracta que contiene una clave primaria sustituta. Pero –

Respuesta

10

Dos entidades son iguales si son del mismo tipo y tienen la misma clave principal.

Si tiene enteros de teclas:

  1. comprobar la igualdad de referencia como lo hace ahora
  2. Si usted tiene el método de Igualdad de alguna clase base se comprueba que los tipos que usted está comparando son iguales. Aquí puede obtener en problemas con los proxies, voy a volver a la
  3. Compruebe si las claves primarias son iguales - que no cause ningún vago de carga

Si tiene GUID para las llaves:

  1. comprobar la igualdad de referencia como lo hace ahora
  2. Compruebe si las claves primarias son iguales - que no cause ningún vago de carga

Si tengo enteros para las llaves que por lo general tienen algo como esto Igualdad-override en una clase base para mis entidades:

public virtual bool Equals(EntityBase other) 
{ 
    if (other == null) 
    { 
     return false; 
    } 

    if (ReferenceEquals(other, this)) 
    { 
     return true; 
    } 

    var otherType = NHibernateProxyHelper.GetClassWithoutInitializingProxy(other); 
    var thisType = NHibernateProxyHelper.GetClassWithoutInitializingProxy(this); 
    if (!otherType.Equals(thisType)) 
    { 
     return false; 
    } 

    bool otherIsTransient = Equals(other.Id, 0); 
    bool thisIsTransient = Equals(Id, 0); 
    if (otherIsTransient || thisIsTransient) 
     return false; 

    return other.Id.Equals(Id); 
} 

Ahora bien, si las entidades que heredan de otras personas que utilizan tabla por jerarquía que se enfrentará el problema de que GetClassWithoutInitializingProxy volverá la base clase de la jerarquía si es un proxy y el tipo más específico si es una entidad cargada. En un proyecto lo solucioné atravesando la jerarquía y, por lo tanto, comparando siempre los tipos de base, proxy o no.

En estos días, aunque yo siempre ir para el uso de GUID como claves y hacer como se describe aquí: http://nhibernate.info/doc/patternsandpractices/identity-field-equality-and-hash-code.html

entonces no hay ningún tipo de proxy problema de falta de coincidencia.

+0

transitorios entidades no tienen una clave principal (como yo uso las claves principales sustitutos, que son fijados por nhibernate al persistente), por lo que entonces la entidad no serían igual a sí mismo antes y después de la persistencia. ¿No sería eso una violación del contrato 'Igual'? –

+0

Pero si son transitorios, dos referencias a la misma instancia serán ReferenceEqual y si no son referencias iguales y ambas tienen 0 o Guid.Empty como clave, entonces está la verificación de esa última en el método anterior. – asgerhallas

+0

¿De qué tipo es su clave sustituta? – asgerhallas

1

que utilice las siguientes reglas:

  1. Si la entidad tiene una propiedad de POID (recordar que no hay necesidad de la propiedad o cualquier miembro simplemente omitir el nombre = "XX", no estoy seguro si activerecord o la estrategia de mapeo está utilizando este supoprt)

    • No transitoria: Si instancia tiene ID = por defecto (idtype), entonces es igual a otra entidad si ambos tienen el mismo ID.
    • transitoria: Si instancia tiene ID == defecto (idtype), entonces es igual a otra entidad si ambos son la misma referencia. ReferenceEquals (esto, otro).
  2. Si la entidad no tiene una propiedad de POID , seguro que va a necesitar algo natural-id. Use la identificación natural para igualdad y GetHashCode.

  3. Si tiene una identificación natural con muchos a uno, en lugar de hacer FooProperty.Equals (other.FooProperty), use FooProperty.Id.Equals (other.FooProperty.Id). El acceso a la ID no activa la inicialización de la referencia perezosa.

Por último, pero no menos importante, el uso de composite-id es desalentar, y la identificación de material compuesto con llave-muchos-a-uno es muy desanime.

2

Si está utilizando la igualdad de identidad, que debe ser capaz de acceder a la clave sin desencadenar una carga:

public virtual bool Equals(ClassB other) 
{ 
    if (ReferenceEquals(null, other)) 
    { 
     return false; 
    } 
    if (ReferenceEquals(this, other)) 
    { 
     return true; 
    } 
    // needs to check for null Id 
    return Equals(other.ClassC.Id, ClassC.Id) && Equals(other.ClassD.Id, ClassD.Id); 
} 

que puede manejar las comparaciones entre los objetos antes y después de la persistencia de almacenamiento en caché el código hash cuando era transitoria . Esto deja una pequeña brecha en el contrato Equals en el sentido de que una comparación entre un objeto existente que era transitorio no generará el mismo código hash que una versión recién recuperada del mismo objeto.

public abstract class Entity 
{ 
    private int? _cachedHashCode; 

    public virtual int EntityId { get; private set; } 

    public virtual bool IsTransient { get { return EntityId == 0; } } 

    public override bool Equals(object obj) 
    { 
     if (obj == null) 
     { 
      return false; 
     } 
     var other = obj as Entity; 
     return Equals(other); 
    } 

    public virtual bool Equals(Entity other) 
    { 
     if (other == null) 
     { 
      return false; 
     } 
     if (IsTransient^other.IsTransient) 
     { 
      return false; 
     } 
     if (IsTransient && other.IsTransient) 
     { 
      return ReferenceEquals(this, other); 
     } 
     return EntityId.Equals(other.EntityId); 
    } 

    public override int GetHashCode() 
    { 
     if (!_cachedHashCode.HasValue) 
     { 
      _cachedHashCode = IsTransient ? base.GetHashCode() : EntityId.GetHashCode(); 
     } 
     return _cachedHashCode.Value; 
    } 
} 
+0

Esto no se ejecutará, ya que EntityId e IsTransient no son virtuales y cuando ejecuta IsTransient cargará el proxy. – asgerhallas

+0

Corté y pegué un código existente y tuve que modificarlo. Esas propiedades son abstractas en el original. Lo editaré. Pero no veo por qué crees que cargará el proxy y no he observado ese comportamiento. –

+0

Perdón por responder tarde :) ...Usando el ejemplo de OP, si tiene una instancia de ClassB y tiene una referencia a ClassA que es un proxy, si hace classB.ClassA.IsTransient, entonces cargará el proxy. Entonces, si tiene classB.ClassA.Equals (someOtherClassA), cargará el proxy si usa su implementación. Puede ir directamente a la propiedad Id (esto no lo cargará tal como indica más arriba), o si desea el acceso directo IsTransient, conviértalo en un método de extensión. – asgerhallas

Cuestiones relacionadas