【问题标题】:Getting proxies of the correct type in NHibernate在 NHibernate 中获取正确类型的代理
【发布时间】:2010-09-29 14:51:57
【问题描述】:

我在 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 映射和数据库架构,而且我可以随意更改它们(只要我不更改模型类之间的关系)。
  • 我有很多类似示例中的方法,我不想全部修改。

谢谢,
尼尔

【问题讨论】:

    标签: c# .net nhibernate proxy


    【解决方案1】:

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

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

    作为一种变体,您也可以使用no-proxy,参见this post

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

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

    您可以在动物主人上使用泛型来使引用成为一个具体的类。

    class AnimalOwner<TAnimal>
    {
      virtual TAnimal OwnedAnimal {get;set;}
    }
    
    class CatOwner : AnimalOwner<Cat>
    {
    }
    
    class DogOwner : AnimalOwner<Dog>
    {
    }
    

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

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

    正如this blog 中所建议的那样,您在 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>();
    

    【讨论】:

    • 谢谢,我不确定我是否可以在我的案例中使用这些技术,但我会检查一下。
    • 添加到其他选项(no-proxyCastEntity)。
    • CastEntity 是一个很好的尝试,但我遇到了一个问题:如果您有 Parent 和 Child 类,并且 Parent 类型的实例“p”实际上是 Child(多态性),则转换失败编译时,即使它可以在运行时工作。另请注意,您的第一个“INhibernateProxy”在“h”上缺少大写字母。切换到“as”演员并没有帮助,但“Self”技巧奏效了。
    • 我不明白你的亲子问题。如果 parent 是 child,child 需要从 parent 派生,编译器应该没有问题。
    • 不幸的是,lazy=no-proxy 仍然存在问题,请参阅nhibernate.jira.com/browse/NH-3047 示例。
    【解决方案2】:

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

    public Animal Self { get { return this; } }
    

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

    【讨论】:

      【解决方案3】:

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

      public virtual T As<T>() where T : Entity {
            return this as T;
      }
      

      【讨论】:

        【解决方案4】:

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

        ((INHibernateProxy)proxy).HibernateLazyInitializer.PersistentClass
        

        但是这种类型的转换或“类型偷看”无论如何都是非常糟糕的做法......

        【讨论】:

          【解决方案5】:

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

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

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

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

          希望对你有帮助

          【讨论】:

          • 这实际上不会再次加载对象 - 除非您同时创建了一个新会话,不推荐这样做。 NH 确保您在会话的整个生命周期内为数据库中的相同实例在内存中获取相同的实例。
          • 我们实际上恢复了上面的“自我”解决方案,因为我们发现它更可靠,尽管这是我们书中给出的解决方法。
          【解决方案6】:

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

          public class DogOwnerMapOverride : IAutoMappingOverride<DogOwner>
          {
              public void Override( AutoMapping<DogOwner> mapping )
              {
                  mapping.References( x => x.OwnedAnimal ).Not.LazyLoad();
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2012-10-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-09-26
            • 1970-01-01
            • 1970-01-01
            • 2011-05-06
            • 1970-01-01
            相关资源
            最近更新 更多