如果您確定不想使用或被阻止使用單個數字主鍵,那麼您需要使用NHibernate CompositeKey functionality映射標識。在這種情況下,您絕對需要實施自定義的覆蓋,以便該表的列值檢查邏輯可以確定標識。 Here is a good article覆蓋GetHashCode
和Equals
方法。您還應該覆蓋平等運算符,以便爲所有用法完成。
來自評論:在哪些情況下默認執行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);
}
}
謝謝,很高興聽到這樣的答案:)。你確定會話身份地圖在某些角落案例中沒有被破解嗎?你有沒有任何例子,當幾個會議的工作是不可避免的? – 2011-05-03 07:47:06
您不應該在同一個會話中獲得代表同一實體的兩個不同對象(如同一類類型和同一數據庫行)。否則,這將是NHibernate中的一個錯誤。有時你可以使用無狀態會話(獨立於主會話)進行批量插入/更新操作或將審計數據寫入數據庫。例如,你不能在持久性攔截器中使用你的主會話。 – 2011-05-03 14:51:31
@DmitryS。你*可以*得到兩個代表同一實體的對象:一個集合和非代理實體中的代理實體。如果非代理引用集合實體,引用相等(NHibernate中的默認值)**失敗**。因此,總是建議覆蓋等於。 – TheCloudlessSky 2013-11-20 23:46:55