NHibernate:重写Equals和GetHashCode的原因

时间:2011-05-01 21:05:09

标签: .net nhibernate

使用NHibernate时,为什么在实体中应该覆盖Equals或GetHashCode?在哪些情况下这些原因有效?

可以在网上找到的一些原因:

  • 支持延迟加载。对比 代理对象通过默认等于 方法可能导致意外的错误。 但这应该通过解决 身份地图(它确实在 很多情况),不应该吗?当使用单个会话中的实体时,即使不重写Equals / GetHashCode,一切也应该正常工作。在那儿 身份地图的任何情况 不好发挥它的作用?
  • 对于NHibernate集合很重要。是否存在GetHashCode的默认实现不充分的情况(不包括与Equals相关的问题)?
  • 混合多个实体 会话和分离的实体。是吗 好主意这样做?

还有其他原因吗?

2 个答案:

答案 0 :(得分:12)

正如您在问题中提到的,实体实例的身份是覆盖Equals&的主要要求。 GetHashCode。 NHibernate中的最佳实践是使用数字键值(short,int或long),因为它简化了将实例映射到数据库行的过程。在数据库世界中,此数值成为主键列值。如果一个表具有所谓的自然键(其中多个列一起唯一地标识一行),那么单个数值可以成为这个值组合的代理主键。

如果您确定不想使用或阻止使用单个数字主键,则需要使用NHibernate CompositeKey functionality映射身份。在这种情况下,您绝对需要实现自定义GetHashCode& Equals覆盖,因此该表的列值检查逻辑可以确定标识。 Here is a good article覆盖GetHashCodeEquals方法。您还应该覆盖等于运算符以完成所有用法。

来自评论:Equals(和GetHashCode)的默认实施在哪些情况下不足?

对于NHibernate,默认实现不够好,因为它基于Object.Equals implementation。此方法将引用类型的相等性确定为引用相等性。换句话说,这两个对象是否指向相同的内存位置?对于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(也可以抽象为any identity type),这是一个示例基类:

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

答案 1 :(得分:7)

如果您正在处理多个会话,分离的实体,无状态会话或集合,那么重载EqualsGetHashCode方法非常重要(请参阅Sixto Saez的答案!)。

在同一会话范围内,身份映射将确保您只拥有同一实体的单个实例。但是,有可能将实体与同一实体的代理进行比较(见下文)。