EqualsおよびGetHashCodeの遅延読み込みNHibernateプロパティ

c# lazy-loading nhibernate
EqualsおよびGetHashCodeの遅延読み込みNHibernateプロパティ

次の問題にどのように対処できますか?

レイジーロードされたhttp://en.wikipedia.org/wiki/NHibernate[NHibernate]プロパティを使用しており、 `Equals()`または `GetHashCode()`を呼び出すたびに、使用されるプロパティはレイジーロードされ、遅延読み込み操作のカスケードを引き起こす可能性があります。 Eager-loadingは代替手段として使用できますが、一般的な解決策としてではなく、特定の場合にのみ考えます。

典型的なシナリオは次のようになります。

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

GetHashCode`と Equals(object) `の実装は簡潔にするために省略されています。

この問題に取り組むためにどのような戦略を使用できますか?

  5  3


ベストアンサー

2つのエンティティは、同じタイプであり、同じ主キーを持っている場合、同等です。

キーに整数がある場合:

  1. 今と同じように参照の平等を確認してください

  2. いくつかの基本クラスにEqualメソッドがある場合は、
    比較するタイプは同じです。 ここで、プロキシのトラブルに巻き込まれる可能性があります。

  3. 主キーが等しいかどうかを確認します-それは何も引き起こしません
    遅延読み込み

キーのGUIDがある場合:

  1. 今と同じように参照の平等を確認してください

  2. 主キーが等しいかどうかを確認します-それは何も引き起こしません
    遅延読み込み

キーに整数がある場合、通常、エンティティの基本クラスに次のような等しいオーバーライドがあります:

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

階層ごとのテーブルを使用して他のエンティティを継承するエンティティを使用すると、GetClassWithoutInitializingProxyがプロキシの場合は階層の基本クラスを返し、ロードされたエンティティの場合はより具体的なタイプを返すという問題に直面します。 あるプロジェクトでは、階層をトラバースし、したがって基本型を常に比較することでそれを回避しました-プロキシかどうか。

最近では、GUIDを常にキーとして使用し、ここで説明するように実行します。http://nhibernate.info/doc/patternsandpractices/identity-field-equality-and-hash-code.html

その後、プロキシタイプの不一致の問題はありません。

10


同一性を使用している場合、ロードをトリガーせずにキーにアクセスできるはずです。

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

一時的なハッシュコードをキャッシュすることで、永続化前後のオブジェクト間の比較を処理できます。 これにより、Equalsコントラクトに小さなギャップが残ります。これは、一時的な既存のオブジェクトとの比較では、同じオブジェクトの新しく取得されたバージョンと同じハッシュコードが生成されないためです。

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

2


私は以下のルールを使います。

  1. エンティティにPOIDプロパティがある場合(必要がないことに注意してください)
    プロパティまたはメンバーは単にname = “XX”を省略します。activerecordまたは使用するマッピング戦略がsupoprtであるかどうかは不明です)

    • *一時的ではありません:*インスタンスのIDが!= default(idType)の場合、
      両方が同じIDを持つ場合、別のエンティティと等しくなります。

    • *一時的:*インスタンスのIDがID == default(idType)の場合、等しい
      両方が同じ参照である場合、別のエンティティに。 ReferenceEquals(this、other)。

  2. エンティティに* POIDプロパティ*がない場合、必ず必要になります
    ナチュラルID。 同等の自然IDとGetHashCodeを使用します。

  3. あなたがするのではなく、多対1の自然IDを持っている場合
    FooProperty.Equals(other.FooProperty)、FooProperty.Id.Equals(other.FooProperty.Id)を使用します。 IDにアクセスしても、遅延参照の初期化はトリガーされません。

最後になりましたが、composite-idを使用することはお勧めできません。また、key-many-to-oneを使用したComposite idは非常にお勧めできません。

1


タイトルとURLをコピーしました