我们应该如何为NHibernate实体实现Equals和GetHashCode

Tom*_*ter 10 .net nhibernate equals gethashcode

这个问题有很多问题和答案以及文章,但我认为似乎没有真正明确/正确的答案

对我来说,Ayende迄今为止已经看到了最好的通用实现:http://ayende.com/blog/2500/generic-entity-equality

....但是从2007年开始....

这是实现这些方法的"最佳方式",特别是NHibernate 3.2,它包含代理实现与早期版本的一些差异吗?

The*_*Sky 5

是!

你应该覆盖EqualsGetHashCode.但是,你应该做值平等(Name == other.Name && Age == other.Age),你应该做同一性身份!

如果不这样做,您很可能会将实体的代理与真实实体进行比较,并且调试起来很麻烦.例如:

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!).
}
Run Code Online (Sandbox Code Playgroud)

如果您使用的intId(也可以抽象为任何身份类型),这是一个示例基类:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @bdrajer问题是,如果你将瞬态实例放在一个长期存在的字典中_before_持久化它们,它们将被编入一个不基于id的哈希码索引.例如,如果实例从NH会话中逐出并重新加载或由不同的会话加载,该实例将根据id计算它的哈希码,并且您将无法在原始字典中使用此实例进行查找.因此,安全地使用基于缓存哈希代码的此实现,确实带有某些规则. (2认同)

Die*_*hon 2

我个人的建议是根本不要实现这些方法,因为这样做会在许多情况下强制加载,而实际上并不需要加载。

另外,如果您不跨会话移动实体,您将永远不需要这个。即使您这样做了,您也可以在需要时通过 Id 进行比较。

  • 完全。但我认为作为一个社区(为了避免调试麻烦!),我们应该始终建议覆盖“Equals”以实现“身份平等”。否则,这些问题*将会*在您不知情的情况下蔓延。例如,直到两天前,我*从未*覆盖过“Equals”。然而,当我从集合中删除一个实体时,我遇到了下面的情况,弄清楚为什么会发生这种情况是一个**真正的痛苦**。延迟加载集合非常常见。另外,我正在使用您可爱的“NHibernate.CollectionQuery”将我所有的域逻辑放入模型中 - 这太棒了:)。 (2认同)