NHibernate: причины переопределения Equals и GetHashCode - PullRequest
16 голосов
/ 02 мая 2011

Существуют ли причины, по которым Equals или GetHashCode должны переопределяться в сущностях при использовании NHibernate?И в каких случаях допустимы эти причины?

Некоторые причины, которые можно найти в Интернете:

  • Поддержка отложенной загрузки.Сравнение прокси-объектов с помощью метода Equals по умолчанию может привести к неожиданным ошибкам.Но это должно быть решено с помощью карты идентичности (и это действительно во многих случаях), не так ли?При работе с сущностями из одного сеанса все должно работать нормально даже без переопределения Equals / GetHashCode.Есть ли случаи, когда идентификационная карта плохо играет свою роль?
  • Это важно для коллекций NHibernate.Существуют ли случаи, когда реализация GetHashCode по умолчанию недостаточна (не включая проблемы, связанные с Equals)?
  • Смешивание объектов из нескольких сеансов и отдельных объектовЭто хорошая идея, чтобы сделать это?

Любые другие причины?

Ответы [ 2 ]

12 голосов
/ 02 мая 2011

Как вы упоминаете в своем вопросе, идентичность экземпляра сущности является основным требованием для переопределения Equals & GetHashCode. В NHibernate рекомендуется использовать числовые значения ключей (short, int или long в зависимости от ситуации), поскольку это упрощает сопоставление экземпляра со строкой базы данных. В мире баз данных это числовое значение становится значением столбца первичного ключа. Если таблица имеет так называемый естественный ключ (где несколько столбцов вместе однозначно определяют строку), то одно числовое значение может стать суррогатным первичным ключом для этой комбинации значений.

Если вы определили, что не хотите использовать или вам запрещено использовать один числовой первичный ключ, вам необходимо сопоставить удостоверение с помощью функциональности NHibernate CompositeKey . В этом случае вам абсолютно необходимо реализовать пользовательские переопределения GetHashCode & Equals, чтобы логика проверки значения столбца для этой таблицы могла определять идентичность. Вот хорошая статья о переопределении методов GetHashCode и Equals. Вы также должны переопределить оператор равенства, чтобы завершить его для всех случаев использования.

Из комментария: В каких случаях по умолчанию реализация EqualsGetHashCode) недостаточна?

Реализация по умолчанию недостаточно хороша для NHibernate, поскольку она основана на реализации Object.Equals . Этот метод определяет равенство для ссылочных типов как ссылочное равенство. Другими словами, эти два объекта указывают на одну и ту же область памяти? Для NHibernate равенство должно основываться на значении (ях) отображения идентичности.

Если вы этого не сделаете, вы, скорее всего, столкнетесь со сравнением прокси-объекта сущности с реальной сущностью, и это будет жалким для отладки . Например:

public class Blog : EntityBase<Blog>
{
    public virtual string Name { get; set; }

    // This would be configured to lazy-load.
    public virtual IList<Post> Posts { get; protected set; }

    public Blog()
    {
        Posts = new List<Post>();
    }

    public virtual Post AddPost(string title, string body)
    {
        var post = new Post() { Title = title, Body = body, Blog = this };
        Posts.Add(post);
        return post;
    }
}

public class Post : EntityBase<Post>
{
    public virtual string Title { get; set; }
    public virtual string Body { get; set; }
    public virtual Blog Blog { get; set; }

    public virtual bool Remove()
    {
        return Blog.Posts.Remove(this);
    }
}

void Main(string[] args)
{
    var post = session.Load<Post>(postId);

    // If we didn't override Equals, the comparisons for
    // "Blog.Posts.Remove(this)" would all fail because of reference equality. 
    // We'd end up be comparing "this" typeof(Post) with a collection of
    // typeof(PostProxy)!
    post.Remove();

    // If we *didn't* override Equals and *just* did 
    // "post.Blog.Posts.Remove(post)", it'd work because we'd be comparing 
    // typeof(PostProxy) with a collection of typeof(PostProxy) (reference 
    // equality would pass!).
}

Вот пример базового класса, если вы используете int в качестве Id (который также может быть абстрагирован до любого типа идентификации ):

public abstract class EntityBase<T>
    where T : EntityBase<T>
{
    public virtual int Id { get; protected set; }

    protected bool IsTransient { get { return Id == 0; } }

    public override bool Equals(object obj)
    {
        return EntityEquals(obj as EntityBase<T>);
    }

    protected bool EntityEquals(EntityBase<T> other)
    {
        if (other == null)
        {
            return false;
        }
        // One entity is transient and the other is not.
        else if (IsTransient ^ other.IsTransient)
        {
            return false;
        }
        // Both entities are not saved.
        else if (IsTransient && other.IsTransient)
        {
            return ReferenceEquals(this, other);
        }
        else
        {
            // Compare transient instances.
            return Id == other.Id;
        }
    }

    // The hash code is cached because a requirement of a hash code is that
    // it does not change once calculated. For example, if this entity was
    // added to a hashed collection when transient and then saved, we need
    // the same hash code or else it could get lost because it would no 
    // longer live in the same bin.
    private int? cachedHashCode;

    public override int GetHashCode()
    {
        if (cachedHashCode.HasValue) return cachedHashCode.Value;

        cachedHashCode = IsTransient ? base.GetHashCode() : Id.GetHashCode();
        return cachedHashCode.Value;
    }

    // Maintain equality operator semantics for entities.
    public static bool operator ==(EntityBase<T> x, EntityBase<T> y)
    {
        // By default, == and Equals compares references. In order to 
        // maintain these semantics with entities, we need to compare by 
        // identity value. The Equals(x, y) override is used to guard 
        // against null values; it then calls EntityEquals().
        return Object.Equals(x, y);
    }

    // Maintain inequality operator semantics for entities. 
    public static bool operator !=(EntityBase<T> x, EntityBase<T> y)
    {
        return !(x == y);
    }
}
7 голосов
/ 03 мая 2011

Перегрузка методов Equals и GetHashCode важна, если вы работаете с несколькими сеансами, отсоединенными объектами, сеансами без сохранения состояния или коллекциями (см. Пример ответа Sixto Saez!).

В том же самомКарта идентификаторов области сеанса гарантирует, что у вас есть только один экземпляр одного и того же объекта.Тем не менее, существует возможность сравнения сущности с прокси той же сущности (см. Ниже).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...