.net - NHibernate:覆盖 Equals 和 GetHashCode 的原因

标签 .net nhibernate

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

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

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

  • 还有其他原因吗?

    最佳答案

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

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

    来自评论:在哪些情况下是 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);
        }
    }
    

    关于.net - NHibernate:覆盖 Equals 和 GetHashCode 的原因,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5851398/

    相关文章:

    c# - 如何将 "var"用法(而不是显式类型)视为警告?

    .net - 尝试使用它作为成员的类型来映射实体

    c# - Nhibernate 不级联删除

    asp.net - 在 Web 应用程序中大量使用 NHibernate Futures——好还是坏?

    .net - 绑定(bind)到数组元素

    c# - 当使用自定义契约(Contract)解析器而不是 JsonConverter 属性时,自定义 JsonConverter 被忽略以进行反序列化

    nhibernate - NHibernate QueryOver 中的 GUID 比较引发错误

    c# - 对 MembershipUser 的流畅 NHibernate 引用

    .net - 需要具有一些外边框和不同内边框的 gridview(.net web 应用程序)

    c# - Entity Framework 核心缺乏适度的 LINQ 查询支持的最佳解决方案是什么?