5 Stimmen

Lazy-loaded NHibernate-Eigenschaften in Equals und GetHashCode

Wie kann das folgende Problem gelöst werden?

Wir verwenden "lazy loaded". NHibernate Eigenschaften und wann immer wir die Equals() o GetHashCode() alle verwendeten Eigenschaften werden "lazy-loaded", was zu einer Kaskade von "lazy-loading"-Vorgängen führen kann. Eager-Loading könnte als Alternative verwendet werden, aber ich denke, nur in bestimmten Fällen und nicht als allgemeine Lösung.

Ein typisches Szenario würde folgendermaßen aussehen:

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

Durchführung von GetHashCode et Equals(object) wurden der Kürze halber weggelassen.

Welche Strategien können zur Lösung dieses Problems eingesetzt werden?

10voto

asgerhallas Punkte 15506

Zwei Entitäten sind gleich, wenn sie vom gleichen Typ sind und den gleichen Primärschlüssel haben.

Wenn Sie ganze Zahlen als Schlüssel haben:

  1. Prüfen Sie die Gleichheit der Referenzen, so wie Sie es jetzt tun
  2. Wenn Sie die Equal-Methode in einer Basisklasse haben, prüfen Sie, ob die Typen, die Sie vergleichen, gleich sind. Hier kann man Probleme mit Proxies bekommen, darauf komme ich zurück
  3. Prüfen Sie, ob die Primärschlüssel gleich sind - das führt nicht zu einem "Lazy-Loading".

Wenn Sie GUIDs für Schlüssel haben:

  1. Prüfen Sie die Gleichheit der Referenzen, so wie Sie es jetzt tun
  2. Prüfen Sie, ob die Primärschlüssel gleich sind - das führt nicht zu einem "Lazy-Loading".

Wenn ich Ganzzahlen für Schlüssel habe, habe ich in der Regel etwas wie dieses Equal-override in einer Basisklasse für meine Entitäten:

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

Wenn Sie nun Entitäten haben, die von anderen erben, indem Sie eine Tabelle pro Hierarchie verwenden, werden Sie mit dem Problem konfrontiert, dass GetClassWithoutInitializingProxy die Basisklasse der Hierarchie zurückgibt, wenn es sich um einen Proxy handelt, und den spezifischeren Typ, wenn es eine geladene Entität ist. In einem Projekt habe ich das umgangen, indem ich die Hierarchie durchlaufen habe und somit immer die Basistypen verglichen habe - Proxy oder nicht.

In diesen Tagen würde ich jedoch immer GUIDs als Schlüssel verwenden und wie hier beschrieben vorgehen: http://nhibernate.info/doc/patternsandpractices/identity-field-equality-and-hash-code.html

Dann gibt es kein Problem mit der Nichtübereinstimmung von Proxy-Typen.

2voto

Jamie Ide Punkte 46985

Wenn Sie Identitätsgleichheit verwenden, sollten Sie in der Lage sein, auf den Schlüssel zuzugreifen, ohne eine Last auszulösen:

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

Sie können Vergleiche zwischen Objekten vor und nach der Persistierung durchführen, indem Sie den Hash-Code zwischenspeichern, wenn er transient war. Dies hinterlässt eine kleine Lücke im "Equals"-Vertrag, da ein Vergleich zwischen einem bestehenden Objekt, das vorübergehend war, nicht denselben Hash-Code erzeugt wie eine neu abgerufene Version desselben Objekts.

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

1voto

José F. Romaniello Punkte 13216

Ich wende die folgenden Regeln an:

  1. Wenn die Entität eine POID-Eigenschaft hat (denken Sie daran, dass keine Eigenschaft oder kein Mitglied erforderlich ist, lassen Sie einfach name="XX" weg; ich bin nicht sicher, ob activerecord oder die von Ihnen verwendete Zuordnungsstrategie dies unterstützt)

    • Nicht flüchtig: Wenn die Instanz ID != default(idType) hat, dann ist sie gleich einer anderen Entität, wenn beide die gleiche ID haben.
    • Vorübergehend: Wenn die Instanz ID == default(idType) hat, dann ist sie gleich einer anderen Entität, wenn beide die gleiche Referenz sind. ReferenceEquals(this, other).
  2. Wenn die Entität keine POID-Eigenschaft benötigen Sie auf jeden Fall eine natürliche Identität. Verwenden Sie natürliche ID für Gleichheit und GetHashCode.

  3. Wenn Sie eine natürliche ID mit many-to-one haben, verwenden Sie statt FooProperty.Equals(other.FooProperty) FooProperty.Id.Equals(other.FooProperty.Id). Der Zugriff auf die ID löst nicht die Initialisierung der lazy reference aus.

Nicht zuletzt ist von der Verwendung von composite-id abzuraten, und von composite-id mit key-many-to-one ist sehr abzuraten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X