19

我在 nhibernate 中遇到了未初始化代理的问题

领域模型

假设我有两个平行的类层次结构:Animal、Dog、Cat 和 AnimalOwner、DogOwner、CatOwner,其中 Dog 和 Cat 都继承自 Animal,而 DogOwner 和 CatOwner 都继承自 AnimalOwner。AnimalOwner 有一个 Animal 类型的引用,称为 OwnedAnimal。

以下是示例中的类:

public abstract class Animal
{
   // some properties
}

public class Dog : Animal
{
   // some more properties
}

public class Cat : Animal
{
   // some more properties
}

public class AnimalOwner 
{
   public virtual Animal OwnedAnimal {get;set;}
   // more properties...
}

public class DogOwner : AnimalOwner
{
   // even more properties
}

public class CatOwner : AnimalOwner
{
   // even more properties
}

这些类具有适当的休眠映射,所有属性都是持久的,并且可以延迟加载的所有内容都是延迟加载的。

应用程序业务逻辑只允许您在 DogOwner 中设置 Dog,在 CatOwner 中设置 Cat。

问题

我有这样的代码:

public void ProcessDogOwner(DogOwner owner)
{
   Dog dog = (Dog)owner.OwnedAnimal;
   ....
}

此方法可以由许多不同的方法调用,在大多数情况下,狗已经在内存中并且一切正常,但很少有狗不在内存中 - 在这种情况下,我得到一个休眠的“未初始化代理”,但演员抛出一个例外,因为 nhibernate 为 Animal 而不是 Dog 生成代理。

我知道这就是 nhibernate 的工作方式,但我需要知道类型而不加载对象 - 或者,更准确地说,我需要未初始化的代理作为 Cat 或 Dog 的代理,而不是 Animal 的代理。

约束

  • 我无法更改领域模型,模型是由另一个部门交给我的,我试图让他们更改模型,但失败了。
  • 实际模型比示例复杂得多,类之间有很多引用,出于性能原因,使用预加载或向查询添加连接是不可能的。
  • 我可以完全控制源代码、hbm 映射和数据库模式,并且可以以任何我想要的方式更改它们(只要我不更改模型类之间的关系)。
  • 我有很多方法,例如示例中的方法,我不想修改所有方法。

谢谢,
尼尔

4

6 回答 6

24

关闭动物类的延迟加载是最简单的。你说它主要是在内存中。

<class name="Animal" lazy="false">
<!-- ... -->
</class>

作为一种变体,您还可以使用no-proxy,请参阅这篇文章

<property name="OwnedAnimal" lazy="no-proxy"/>

据我所知,它仅在AnimalOwner实际是代理时才有效。

或者

您可以在动物所有者上使用泛型来使引用成为具体类。

class AnimalOwner<TAnimal>
{
  virtual TAnimal OwnedAnimal {get;set;}
}

class CatOwner : AnimalOwner<Cat>
{
}

class DogOwner : AnimalOwner<Dog>
{
}

或者

您可以将DogOwners和映射到CatOwners单独的表中,并在映射中定义具体的动物类型。

<class name="CatOwner">
  <!-- ... -->
  <property name="OwnedAninal" class="Cat"/>
</class>
<class name="DogOwner">
  <!-- ... -->
  <property name="OwnedAninal" class="Dog"/>
</class>

或者

正如本博客中所建议的那样,您对 NHibernate 有点混乱。NH 实际上能够返回代理背后的真实对象。这里提出了一个更简单的实现:

    public static T CastEntity<T>(this object entity) where T: class
    {
        var proxy = entity as INHibernateProxy;
        if (proxy != null)
        {
            return proxy.HibernateLazyInitializer.GetImplementation() as T;
        }
        else
        {
            return entity as T;
        }
    }

可以这样使用:

Dog dog = dogOwner.OwnedAnimal.CastEntity<Dog>();
于 2009-04-16T12:28:54.097 回答
12

我认为我们最近遇到了类似的问题,AFAIR 的解决方案是给“动物”一个自我——“方法/属性”:

public Animal Self { get { return this; } }

然后可以将其转换为纠正“动物”。发生的情况是您的原始对象具有对 nhibernate 代理对象的引用(当它被延迟加载时),它充当通过 Animal 类公开的所有方法的 Animal(它将所有调用传递给加载的对象)。但是,它不能被转换为您的任何其他动物,因为它不是这些,它只是模仿 Animal 类。但是,AnimalProxy 封装的类可以转换为子类动物,因为它是正确类的真实实例,您只需要获取它的this引用即可。

于 2009-04-20T18:20:14.130 回答
1

您可以尝试将此方法放在您的基础实体上:

public virtual T As<T>() where T : Entity {
      return this as T;
}
于 2015-08-31T18:51:28.413 回答
0

您可能想试试这个来查看代理类型(假设 NH 2.0+):

((INHibernateProxy)proxy).HibernateLazyInitializer.PersistentClass

但无论如何,这种铸造或“类型偷看”是非常糟糕的做法......

于 2009-02-18T00:52:40.973 回答
0

如果我们一直在处理同样的问题,那么问题是生成的代理是 Animal 的代理而不是 Dog 的代理。

我们使用的解决方案是重新加载对象:

Dog dog = this.CurrentSession.Load<Dog>(owner.OwnedAnimal.AnimalID);

这将返回到您的会话并使用正确的类型重新加载对象。

希望这可以帮助

于 2010-07-21T14:46:16.503 回答
0

如果您使用 Fluent NHibernate,您可以使用自动映射覆盖来关闭该属性的延迟加载:

public class DogOwnerMapOverride : IAutoMappingOverride<DogOwner>
{
    public void Override( AutoMapping<DogOwner> mapping )
    {
        mapping.References( x => x.OwnedAnimal ).Not.LazyLoad();
    }
}
于 2014-06-16T18:32:07.473 回答