Lazy-загруженные свойства NHibernate в Equals и GetHashCode - PullRequest
5 голосов
/ 15 февраля 2011

Как можно решить следующую проблему?

Мы используем лениво загруженные NHibernate свойства и всякий раз, когда мы вызываем Equals() или GetHashCode(), любые используемые свойствабыть загруженным с отложенной загрузкой, потенциально вызывая каскад операций отложенной загрузки.Стремительная загрузка может быть использована в качестве альтернативы, но я думаю, что только в конкретных случаях, а не в качестве общего решения.

Типичный сценарий будет выглядеть так:1012 * и Equals(object) опущены для краткости.

Какие стратегии могут быть использованы для решения этой проблемы?

Ответы [ 3 ]

10 голосов
/ 15 февраля 2011

Два объекта равны, если они одного типа и имеют одинаковый первичный ключ.

Если у вас есть целые числа для ключей:

  1. Проверьте равенство ссылок, как сейчас
  2. Если у вас есть метод Equal в каком-то базовом классе, вы проверяете, чтотипы, которые вы сравниваете, равны.Здесь вы можете столкнуться с проблемами с прокси, я вернусь к этому
  3. Проверьте, равны ли первичные ключи - это не вызовет ленивой загрузки

Если у вас естьGUID для ключей:

  1. Проверьте равенство ссылок, как вы делаете сейчас
  2. Проверьте, равны ли первичные ключи - это не приведет к отложенной загрузке

Если у меня есть целые числа для ключей, у меня обычно есть что-то вроде этого Equal-override в базовом классе для моих сущностей:

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

Тогда проблем с несоответствием типов прокси нет.

2 голосов
/ 15 февраля 2011

Если вы используете равенство идентификаторов, у вас должна быть возможность доступа к ключу без запуска загрузки:

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;
    }
}
1 голос
/ 15 февраля 2011

Я использую следующие правила:

  1. Если у сущности есть свойство POID (помните, что свойство не нужно, или какой-либо член, просто опустите name = "XX", не уверенесли activerecord или стратегия отображения, которую вы используете выше, это)

    • Не переходный процесс: Если экземпляр имеет идентификатор! = default (idType), то он равен другому объекту, если оба имеюттот же идентификатор.
    • Переходный процесс: Если экземпляр имеет идентификатор == default (idType), то он равен другому объекту, если оба являются одним и тем же эталоном.ReferenceEquals (это, другое).
  2. Если у сущности нет свойства POID , вам наверняка понадобится натуральный идентификатор.Используйте естественный идентификатор для равенства и GetHashCode.

  3. Если у вас есть естественный идентификатор со многими к одному, вместо выполнения FooProperty.Equals (other.FooProperty) используйте FooProperty.Id.equals (other.FooProperty.Id).Доступ к идентификатору не вызывает инициализацию отложенной ссылки.

И последнее, но не менее важное: использование составного идентификатора не рекомендуется, а составной идентификатор с ключом-многие-к-одному -очень обескуражить.

...