在NHibernate 3.0 Cookbook中,有一个基本实体类型的示例实现。 equals实现如下:
public abstract class Entity<TId>
{
public virtual TId Id { get; protected set; }
public override bool Equals(object obj)
{
return Equals(obj as Entity<TId>);
}
private static bool IsTransient(Entity<TId> obj)
{
return obj != null && Equals(obj.Id, default(TId));
}
private Type GetUnproxiedType()
{
return GetType();
}
public virtual bool Equals(Entity<TId> other)
{
if (other == null) return false;
if (ReferenceEquals(this, other)) return true;
if (!IsTransient(this) && !IsTransient(this) && Equals(Id, other.Id))
{
var otherType = other.GetUnproxiedType();
var thisType = GetUnproxiedType();
return thisType.IsAssignableFrom(otherType) ||
otherType.IsAssignableFrom(thisType);
}
return false;
}
}
GetUnproxiedType()方法的原因是:有一个抽象基类Product,一个继承自Product的具体类Book和一个由NHibernate用于延迟加载的动态代理类ProductProxy。如果表示书籍和具体书籍的ProductProxy具有相同的ID,则应将它们视为相等。但是我真的不明白为什么在这个案例中调用ProductProxy实例上的GetType()应该返回Product,以及它如何帮助。有什么想法吗?
答案 0 :(得分:7)
我实际上已经向本书的作者写了关于此代码的内容。事实证明这是由于代理包装的工作原理。以下是他的回答:
“如果你不理解代理框架是如何工作的,那么这个想法看起来很神奇。
当NHibernate为延迟加载返回代理时,它返回从实际类型继承的代理实例。我们可以访问一些成员,而无需强制从数据库加载。其中包括代理的Id属性或字段GetType()
,在某些情况下Equals()
和GetHashCode()
。访问任何其他成员将强制从数据库加载。
当发生这种情况时,代理会创建一个内部实例。因此,例如,一个延迟加载的Customer
(CustomerProxy102987098721340978
)实例,在加载时,将在内部创建一个包含数据库中所有数据的新Customer
实例。代理然后做这样的事情:
public overrides string Name
{
get {
return _loadedInstance.Name;
}
set { _loadedInstance.Name = value; }
}
顺便说一句,正是这个覆盖要求所有内容都是允许延迟加载的实体上的虚拟内容。
因此,对代理上的Name属性的所有调用都被中继到具有实际数据的内部Customer
实例。
GetUnproxiedType()
利用了这一点。在代理上对GetType()
进行简单调用即会返回typeof(CustomerProxy02139487509812340)
。对GetUnproxiedType()
的调用将被中继到内部客户实例,内部客户实例将返回typeof(Customer)
。“
答案 1 :(得分:2)
我们使用NH 2,这个例子对我们不起作用。 (它未能解析类型和左代理类型,见下文)。 它说具有相同id的2个实体不相等,其中一个是代理(COrganization)而另一个不是(DOrganization)。 当我们有一个层次结构时:
class Organization
class AOrganization : Organization
class COrganization : Organization
{
public virtual COrganization GetConcrete()
{
return null;
}
}
class DOrganization : COrganization
{
public virtual COrganization GetConcrete()
{
return this;
}
}
AOrganization aOrganization;
COrganization cOrganization;
contract = new CContract(aOrganization, cOrganization as COrganization); //(COrganization)(cOrganization.GetConcrete()),
所以CContract有一个COrganization类型的领域。使用setter
public class Contract: Entity <short>
{
public virtual COrganization COrganization
{
get { return cOrganization; }
protected internal set
{
if (cOrganization != null && value != cOrganization) // != calls ==, which calls Equals, which calls GetUnproxiedType()
throw new Exception("Changing organization is not allowed.");
}
cOrganization = value;
}
}
private COrganization cOrganization;
}
我们构建了新的Contract,它的构造函数将COrganization字段设置为指向某个组织。然后我们调用UnitOfWork.Commit,NH尝试再次设置COrganization字段(具有相同的id),GetUnproxiedType工作不正确,新旧值被识别为不相等,并抛出异常......
这是出现错误的地方:
var otherType = other.GetUnproxiedType();
var thisType = GetUnproxiedType();
return thisType.IsAssignableFrom(otherType) ||
otherType.IsAssignableFrom(thisType);
在调试器中:otherType == COrganizationProxy - GetUnproxiedType失败... thisType == DOrganization
COrganizationProxy和DOrganization都继承了COrganization。 所以他们彼此不是IsAssignableFrom ......
为什么这个例子适合你?
也许是因为我们有NH 2.0或2.1?
或者因为简单的“cOrganization as COrganization”而不是“(COrganization)(cOrganization.GetConcrete())”?
或者因为我们不仅在实体中实现了==,!=和Equals,而且在组织中也是如此?
public abstract class Organization : Entity<int>
{
public override bool Equals(object obj)
{
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static bool operator ==(Organization object1, Organization object2)
{
return AreEqual(object1, object2);
}
public static bool operator !=(Organization object1, Organization object2)
{
return AreNotEqual(object1, object2);
}
}
public abstract class Entity<TId>
{
public virtual TId Id { get; /*protected*/ set; }
public override bool Equals(object obj)
{
return Equals(obj as Entity<TId>);
}
private static bool IsTransient(Entity<TId> obj)
{
return obj != null &&
Equals(obj.Id, default(TId));
}
private Type GetUnproxiedType()
{
return GetType();
}
public virtual bool Equals(Entity<TId> other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (!IsTransient(this) &&
!IsTransient(other) &&
Equals(Id, other.Id))
{
var otherType = other.GetUnproxiedType();
var thisType = GetUnproxiedType();
return thisType.IsAssignableFrom(otherType) ||
otherType.IsAssignableFrom(thisType);
}
return false;
}
public override int GetHashCode()
{
if (Equals(Id, default(TId)))
return base.GetHashCode();
return Id.GetHashCode();
}
/// This method added by me
/// For == overloading
protected static bool AreEqual<TEntity>(TEntity entity1, TEntity entity2)
{
if ((object)entity1 == null)
{
return ((object)entity2 == null);
}
else
{
return entity1.Equals(entity2);
}
}
/// This method added by me
/// For != overloading
protected static bool AreNotEqual<TEntity>(TEntity entity1, TEntity entity2)
{
return !AreEqual(entity1, entity2);
}
}
答案 2 :(得分:1)
使用当前的(v5.x)NHibernate代理工厂(静态或动态,从v5.1起可以使用静态),这种模式实际上是无效的。 v5内置代理工厂不会拦截私有方法。
我认为v4的情况已经如此。
为使此模式与当前内置的代理工厂一起使用,GetUnproxiedType
应该为virtual
(顺便说一下,private
不能为protected
)。
否则,请使用NHibernateUtil.GetClass
,这是为此目的而已,并且不依赖于易碎的技巧。它的文档警告说,它将通过副作用初始化代理,但是无论如何,GetUnproxiedType
技巧都必须做同样的工作。
当然,使用NHibernateUtil.GetClass
意味着在域模型基类中直接依赖NHibernate。但我认为,取决于特定于外部(从域的角度)库实现的实现技巧,
此外,某些更改可能会导致GetUnproxiedType
窍门在将来更加严重,例如,一些用于减少导致代理被避免的情况下初始化的案例数量的想法。 (例如,请参见here。)
如果您真的想使用GetUnproxiedType
方法而不依赖于直接的NHibernate引用,我认为理论上唯一“安全”的解决方案是在每个具体实体类中对其进行抽象和覆盖以产生typeof(YourEntityClass)
。但是在实践中,这将很麻烦且容易出错(创建新实体时复制粘贴不正确,忘记更改该方法...),而如果某些具体的实体类被进一步专门化,那么抽象部分将无济于事。通过继承。
从GetType
获得的类型中,另一个技巧可能是检查它属于哪个程序集(代理类型将不属于您的程序集中的一个),以在属于该程序集的层次结构中搜索第一个类型。您的域模型装配。
请注意,如果代理是基类的代理,而不是具体类的代理,并且您的辅助方法设置为私有,则它将生成基类类型,而无需初始化代理。在性能方面,这更好。尽管虚拟GetUnproxiedType
仅返回GetType
会返回当前代理工厂的具体类类型,但也会初始化代理。