.net NHibernate:重写Equals和GetHashCode的原因

.net NHibernate:重写Equals和GetHashCode的原因,.net,nhibernate,.net,Nhibernate,使用NHibernate时,实体中应该重写Equals或GetHashCode有什么原因吗?这些理由在哪些情况下有效 可以在web上找到一些原因: 支持延迟加载。比较 通过默认值Equals代理对象 方法可能导致意外的错误。 但这应该通过以下方式解决: 身份图(它确实在 很多情况下),不是吗?使用单个会话中的实体时,即使不重写Equals/GetHashCode,一切都应该正常工作。有 任何情况下,当身份映射 演得不好,这是什么角色 这对于NHibernate系列非常重要。是否存在GetHas

使用NHibernate时,实体中应该重写Equals或GetHashCode有什么原因吗?这些理由在哪些情况下有效

可以在web上找到一些原因:

  • 支持延迟加载。比较 通过默认值Equals代理对象 方法可能导致意外的错误。 但这应该通过以下方式解决: 身份图(它确实在 很多情况下),不是吗?使用单个会话中的实体时,即使不重写Equals/GetHashCode,一切都应该正常工作。有 任何情况下,当身份映射 演得不好,这是什么角色
  • 这对于NHibernate系列非常重要。是否存在GetHashCode的默认实现不足的情况(不包括与Equals相关的问题)
  • 混合多个实体 会话和分离实体。它是 这样做好吗

还有其他原因吗?

正如您在问题中提到的,实体实例的标识是重写
Equals
&
GetHashCode
的主要要求。在NHibernate中,使用数字键值(短、int或长,视情况而定)是最佳做法,因为它简化了实例到数据库行的映射。在数据库世界中,此数值将成为主键列值。如果一个表具有所谓的自然键(其中多个列共同唯一地标识一行),则单个数值可以成为此值组合的代理主键

如果确定不想使用或被阻止使用单个数字主键,则需要使用NHibernate映射标识。在这种情况下,您绝对需要实现自定义
GetHashCode
&
Equals
覆盖,以便该表的列值检查逻辑可以确定标识。重写
GetHashCode
Equals
方法时。您还应该重写equal运算符,使其在所有用途下都是完整的

注释:在哪些情况下,
的默认实现等于
(和
GetHashCode
)不足

默认实现对于NHibernate来说不够好,因为它基于。此方法将引用类型的相等性确定为引用相等性。换句话说,这两个对象是否指向同一个内存位置?对于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!).
}

如果您使用多个会话、分离的实体、无状态会话或集合(请参阅Sixto Saez的答案以获取示例!),则重载
Equals
GetHashCode
方法非常重要


在同一会话中,作用域标识映射将确保您只有同一实体的单个实例。但是,可以将一个实体与同一实体的代理进行比较(见下文)。

谢谢您的回答。我的问题不够清楚。在哪些情况下,Equals(和GetHashCode)的默认实现不足?当使用连接到单个会话的实体时,由于NH identity cache,一切都会顺利进行,除非identity cache以某种方式被破坏(我很好奇为什么缓存会被破坏)。混合分离的实体和通过多个会话检索到的实体就像在我看来使用剪刀一样:)。什么时候有必要这样做?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();谢谢,很高兴听到这样的回答:)。您确定会话标识映射在某些情况下不会被破坏吗?您是否有过处理多个会话不可避免的例子?您不应该得到表示同一实体的两个不同对象(如在同一类类型和同一数据库行中)在同一个会话中。否则,它将是NHibernate中的一个bug。有时您可能会使用无状态会话(独立于主会话)用于批插入/更新操作或将审核数据写入数据库。例如,您不能在持久性拦截器内使用主会话。@DmitryS。您可以获得两个表示同一实体的对象:集合中的代理实体和非代理实体。如果非代理指代集合实体,请参考equality(NHibernate中的默认值)失败。因此,始终建议覆盖equals。“但是,有可能比较…”-直到出现相反的示例-我不同意。如果我加载(子项-)实体作为代理,稍后我访问父对象子集合,子集合包含代理对象和集合中的其他非代理对象。通过NHibernate 5和bags验证。也许另一个senario可以提供其他结果…我完全同意“在同一会话中,作用域标识映射将确保您只有同一实体的单个实例。”
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);
    }
}