等于实现NHibernate实体,unproxy问题

时间:2011-04-16 12:49:51

标签: c# .net nhibernate

在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,以及它如何帮助。有什么想法吗?

3 个答案:

答案 0 :(得分:7)

我实际上已经向本书的作者写了关于此代码的内容。事实证明这是由于代理包装的工作原理。以下是他的回答:

“如果你不理解代理框架是如何工作的,那么这个想法看起来很神奇。

当NHibernate为延迟加载返回代理时,它返回从实际类型继承的代理实例。我们可以访问一些成员,而无需强制从数据库加载。其中包括代理的Id属性或字段GetType(),在某些情况下Equals()GetHashCode()。访问任何其他成员将强制从数据库加载。

当发生这种情况时,代理会创建一个内部实例。因此,例如,一个延迟加载的CustomerCustomerProxy102987098721340978)实例,在加载时,将在内部创建一个包含数据库中所有数据的新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会返回当前代理工厂的具体类类型,但也会初始化代理。