这个问题有很多问题和答案以及文章,但我认为似乎没有真正明确/正确的答案
对我来说,Ayende 拥有迄今为止我见过的最好的通用实现:http: //ayende.com/blog/2500/generic-entity-equality
....但它是从 2007 年开始的 ....
这是实现这些方法的“最佳方式”,尤其是对于 NHibernate 3.2,它包含代理实现与早期版本的一些差异?
这个问题有很多问题和答案以及文章,但我认为似乎没有真正明确/正确的答案
对我来说,Ayende 拥有迄今为止我见过的最好的通用实现:http: //ayende.com/blog/2500/generic-entity-equality
....但它是从 2007 年开始的 ....
这是实现这些方法的“最佳方式”,尤其是对于 NHibernate 3.2,它包含代理实现与早期版本的一些差异?
你应该覆盖Equals
and GetHashCode
。但是,你不应该做价值平等(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!).
}
这是一个示例基类,如果您int
用作您的Id
(也可以抽象为任何身份类型):
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);
}
}
我个人的建议是根本不要实现这些方法,因为这样做会在很多情况下强制加载,而实际上并没有必要。
此外,如果您不跨会话移动实体,您将永远不需要这个。即使你这样做了,你也可以在需要时按 ID 进行比较。
在实施@TheCloudlessSky建议的解决方案时,我遇到了多个问题。
首先,我的 ID 的数据类型不一致;有些是int
,有些是Guid
,有些是string
。此外,有些是自动生成的,而另一些是手动分配的。如果我决定使用复合 ID,将来可能会出现其他问题。因此,我不能把
public virtual int Id { get; protected set; }
在EntityBase
基类中。我必须在各自的具体实体类中定义它。
其次,由于我在基类中无法拥有Id
,因此实现bool IsTransient
属性变得越来越困难。
因此,我决定为Guid
每个实例生成并使用它来实现GetHashCode
,Equals
如下所示:
public abstract class BaseEntity
{
Guid objectId = Guid.NewGuid();
public virtual Guid ObjectId { get { return objectId; } }
public override int GetHashCode()
{
return ObjectId.GetHashCode();
}
public override bool Equals(object other)
{
if(other == null)
return false;
if(ObjectId != (other as BaseEntity).ObjectId)
return false;
return ReferenceEquals(this, other);
}
}