【问题标题】:NHibernate: Reasons for overriding Equals and GetHashCodeNHibernate:重写 Equals 和 GetHashCode 的原因
【发布时间】:2011-08-16 14:55:38
【问题描述】:

在使用 NHibernate 时,是否有任何理由应该在实体中覆盖 Equals 或 GetHashCode?这些理由在哪些情况下有效?

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

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

还有其他原因吗?

【问题讨论】:

    标签: .net nhibernate


    【解决方案1】:

    正如您在问题中提到的,实体实例的身份是覆盖 EqualsGetHashCode 的主要要求。 NHibernate 中的最佳实践是使用数字键值(short、int 或 long,视情况而定),因为它简化了将实例映射到数据库行的过程。在数据库世界中,这个数值成为主键列值。如果表具有所谓的自然键(其中几列共同唯一标识一行),则单个数值可以成为这种值组合的代理主键。

    如果您确定不想使用或被阻止使用单个数字主键,则需要使用 NHibernate CompositeKey functionality 映射身份。在这种情况下,您绝对需要实现自定义 GetHashCodeEquals 覆盖,以便该表的列值检查逻辑可以确定身份。 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);
        }
    }
    

    【讨论】:

    • 感谢您的回答。我的问题不够清楚。在哪些情况下 Equals(和 GetHashCode)的默认实现不足?当使用附加到单个会话的实体时,由于 NH 身份缓存,除非身份缓存以某种方式被破坏(我很好奇为什么缓存会被破坏),否则一切都应该顺利进行。混合分离的实体和通过多个会话检索的实体对我来说就像用剪刀运行:)。什么时候需要做这样的事情?
    • Remove() 缺少一个小细节,不清楚 Blog 是否附加到当前会话。如果附加了博客,则删除应该可以工作。此代码无需实现 Equals/GetHashCode 即可工作: using (var session = CreateSession()) { var post0 = session.Get(postId0); var blog = session.Load(blogId); Assert.IsTrue(blog.Posts[0] 是 INHibernateProxy); Assert.IsFalse(blog.Posts[1] 是 INHibernateProxy); blog.Posts.Remove(post0); session.Flush();
    【解决方案2】:

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

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

    【讨论】:

    • 谢谢,很高兴听到这样的回答:)。您确定会话身份映射在某些极端情况下没有损坏吗?你有什么例子是不可避免地要处理多个会话吗?
    • 你永远不应该在同一个会话中获得 2 个不同的对象来代表同一个实体(在同一个类类型和同一个数据库行中)。否则,这将是 NHibernate 中的一个错误。有时您可能会使用无状态会话(独立于主会话)来执行批量插入/更新操作或将审计数据写入数据库等操作。例如,您不能在持久性拦截器中使用主会话。
    • @DmitryS。您可以获得两个代表同一实体的对象:集合中的代理实体和非代理实体。如果非代理引用集合实体,则引用相等(NHibernate 中的默认值)失败。因此,始终建议覆盖 equals。
    • “但是,有可能比较......” - 直到一个相反的例子 - 我不同意。如果我加载一个(子)实体作为代理,然后我访问父对象子集合,则子集合包含代理对象和集合的其他非代理对象。经 NHibernate 5 和包证明。也许另一个 senario 可能会提供其他结果......我完全同意“在同一个会话范围内,身份映射将确保您只有一个相同实体的实例。”
    猜你喜欢
    • 1970-01-01
    • 2017-10-17
    • 1970-01-01
    • 1970-01-01
    • 2012-04-07
    • 2019-02-02
    相关资源
    最近更新 更多