在正常的对象操作中,当两个对象都是通过 new 或者其它方式创建出来,尽管它们在属性和行为上是一致的,但我们还是得承认他们是属于不同的两个事物,就像现实世界中的单卵双生的双胞胎虽然各方面都很像,但他们到底还是属于不同的个体。但当这种情况发生在 NHibernate(以下简称NH), 实休类中时,我们就不能这么草率地下结论了,通常实体类中每个实例映射着数据库表中的一行记录,我们知道一个良好设计的数据库表字段中一个唯一标识是必不可少的,那就是主键,主键相当于每个公民的身份证号,必须是唯一且无重复的,不管运行多少次 select * from table where id = @id ,只要 @id 值不改变我们就认为查询出来的是同一行记录,同样的道理映射到实体类中也是主键的属性值相等我们就应该承认它们是相等的,一张身份证它可以有多个复印件,但不管怎样它都是属于同一个人的。
由于 System.Object 中是通过 Equals() 方法来确定两个对象是否相等,因为 Equals() 方法中两个对象具有相同的“值”,那么即使它们不是同一实例,这样的 Equals 实现仍返回 true。两个相同的对象它的 GetHashCode() 结果也必须是相等的,在 MSDN 上也明确指出,“重写 Equals 的类型也必须重写 GetHashCode;否则,Hashtable 可能不正常工作。”“重写 GetHashCode 的派生类还必须重写 Equals,以保证被视为相等的两个对象具有相同的哈希代码;否则,Hashtable 可能不会正常工作。”
下面是引用 Hibernate参考文档 第4章 中对 实现equals()和hashCode() 的解释:
4.3. 实现equals()和hashCode()如果你需要混合使用持久化类(比如,在一个Set中),你必须重载equals() 和 hashCode()方法。
这仅适用于那些在两个不同的Session中装载的对象,Hibernate在单个Session中仅保证JVM 辨别( a == b ,equals()的默认实现)!
就算两个对象a和b实际是同一行数据库内容(它们拥有同样的主键值作为辨识符),我们也不能保证在特定的Session 之外它们是同一个Java实例。
最显而易见的实现equals()/hashCode()方法的办法就是比较两个对象的标识值。如果这个值是同堂的,他们必定是直线同一条数据库行,所以它们是相等的(如果都被加入到Set,在Set中只应该出现一个元素)。不幸的是,我们不能使用这种办法。Hibernate只会对已经持久化的对象赋予标识值,新创建的实例将不会有任何标识符值!我们推荐使用商业关键字相等原则来实现equals()和hashCode()。
商业关键字相等意味着equals()方法只比较那些组成商业关键字的属性,它对应着真实世界中的实例(自然的候选关键字)
下面是实现方法(该方法出自 http://cs.nerdbank.net/blogs/jmpinline/archive/2006/01/24/126.aspx ):
/// Tests whether this and another object are equal in a way that
/// will still pass when proxy objects are being used.
/// </summary>
public override bool Equals( object obj)
{
ClassNameHere other = obj as ClassNameHere;
if (other == null) return false;
if (Id == 0 && other.Id == 0)
return (object)this == other;
else
return Id == other.Id;
}
public override int GetHashCode()
{
if (Id == 0) return base.GetHashCode();
string stringRepresentation = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "#" + Id.ToString(); return stringRepresentation.GetHashCode();
}
伴随着 .NET 2.0 的发布,加入了泛型代码中 default 关键字,我们可以把 snippet 做得更加通用而不仅限于 Int 数据类型,
< CodeSnippets xmlns ="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet" >
< CodeSnippet Format ="1.0.0" >
< Header >
< Title > Entity Equals and GetHashCode </ Title >
< Shortcut > nheq </ Shortcut >
< Author > yyw </ Author >
< HelpUrl >
http://www.cnblogs.com/yyw84/archive/2006/09/17/506695.html
http://cs.nerdbank.net/blogs/jmpinline/archive/2006/01/24/126.aspx
</ HelpUrl >
< Description ></ Description >
< SnippetTypes >
< SnippetType > Expansion </ SnippetType >
</ SnippetTypes >
< Description > Inserts the Equals and GetHashCode methods that NHibernate requires to run correctly. </ Description >
</ Header >
< Snippet >
< Declarations >
< Literal Editable ="true" >
< ID > type </ ID >
< ToolTip > The name of the class being injected. </ ToolTip >
< Default > ClassName </ Default >
</ Literal >
< Literal >
< ID > id </ ID >
< ToolTip > 主键ID </ ToolTip >
< Default > Id </ Default >
</ Literal >
< Literal >
< ID > idtype </ ID >
< ToolTip > 主键属性类型 </ ToolTip >
< Default > int </ Default >
</ Literal >
</ Declarations >
< Code Language ="CSharp" >
<![CDATA[ #region Equals and GetHashCode
/// <summary>
/// Tests whether this and another object are equal in a way that
/// will still pass when proxy objects are being used.
/// </summary>
public override bool Equals(object obj)
{
$type$ other = obj as $type$;
if (other == null)
return false;
if ($id$== default($idtype$) && other.$id$ == default($idtype$))
return (object)this == other;
else
return $id$ == other.$id$;
}
public override int GetHashCode()
{
if ($id$ == default($idtype$))
return base.GetHashCode();
string stringRepresentation = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "#" + $id$.ToString();
return stringRepresentation.GetHashCode();
}
#endregion
$end$ ]]>
</ Code >
</ Snippet >
</ CodeSnippet >
</ CodeSnippets >
为了做到一劳永逸,接下来要做的事就是把它添加到我的 CodeSmith 模板中了,添加的内容如下:
/**//// <summary>
/// Tests whether this and another object are equal in a way that
/// will still pass when proxy objects are being used.
/// </summary>
public override bool Equals(object obj)
{
<%= ClassName(SourceTable) %> other = obj as <%= ClassName(SourceTable) %>;
if (other == null)
return false;
if (<%= IdName(SourceTable) %> == default(<%= IdMemberType(SourceTable) %>) && other.<%= IdName(SourceTable) %> == default(<%= IdMemberType(SourceTable) %>))
return (object)this == other;
else
return <%= IdName(SourceTable) %> == other.<%= IdName(SourceTable) %>;
}
public override int GetHashCode()
{
if (<%= IdName(SourceTable) %> == default(<%= IdMemberType(SourceTable) %>))
return base.GetHashCode();
string stringRepresentation = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "#" + <%= IdName(SourceTable) %>.ToString();
return stringRepresentation.GetHashCode();
}
#endregion
事情都已经做到这个份上也该收场了,虽然只是一些小技巧,也希望能给大家带来方便,提高生产效率。。。 :)