为什么Nhibernate中属性和方法必须Virtual的

时间:2022-02-22 19:08:44

如果你曾经用过NHibernate 2.0或者更高的版本,那您一定碰到过下面的错误:NHibernate.InvalidProxyTypeException: The following types may not be used as proxies:
NHibernateExamples.Entities.OrderLine: method get_UnitPrice should be 'public/protected virtual' or 'protected internal virtual'
NHibernateExamples.Entities.OrderLine: method set_UnitPrice should be 'public/protected virtual' or 'protected internal virtual'

哎呀,我们忘记把OrderLine实体中的UnitPrice属性标志成virtual的了,奇怪的是,为什么它一开始的时候必须要设置成virtual的,这是一个对于初次接触NHibernate的人经常有的疑惑。

针对这个问题,最简洁的答案就是:因为我们需要把成员设置成virtual的,是为了实现我们的延迟加载的魔幻功能。

但是更详细点的答案反而更有趣,我们知道任何真正的ORM必须要有的一个重要功能就是延迟加载,如果你通过ORM获取一个对象,你不会希望它去自动的获取整个对象图中所有数据(默认情况下应当不是), 还有你也不希望添加一些凌乱的代码去检查特定的关联关系是否已经被加载了,我们需要的是只有我们的需要的时候才去加载它们。这应该是ORM的职责。理想情况下应该是,你能访问这些属性,而且如果这时数据还没有加载,当你第一次访问的时候,ORM会负责加载当前需要的数据。

NHiernate 拥有这项能力,但是它却不要求你继承NHibernate中的某项基类或者实现一些接口。那奇怪了为什么Nhibernate中属性和方法必须Virtual的? Nhibernate是怎么做到的呢?其实,当需要延迟加载时,在运行期,Nhibernate用的是你的类的代理(proxy)。 那代理又是什么呢? 一个Nhibernate代理指的是当你的程序初始化Nhibernate的时候(发生在应用程序第一次启动的时候,并且只发生一次),Nhibernate动态生成的一个类型。如果你没有显示的给你的Entity指定不需要延迟加载,那Nhibernate会为你的每一个Entity生成一个代理,每一个Entity代理的类型其实是继承与这个Entity的。这样你在操作这个类型的时候,你就可以做一些拦截操作。

为了使这个过程变得更清晰,我们举个小例子。假设你有一个Order类,这个Order类有一些属性如Employee,Customer和其它。 但是当你加载Order实体的时候,你也许不想让Employee属性已经包含了真正的Employee实例。对于Cutomer属性也是同样的。默认情况下,Nhibernate认为每个实体类型都是要延迟加载的,除非你显示的告诉它不用延迟加载。所以,当Nhibernate一开始初始化的时候,它会知道它需要动态的为Customer和Employee生成代理(proxy)类型。在这我们假设两个类型的名字为CustomerProxyType和EmployeeProxyType (在现实中它们不会叫这个名字,但是这个不重要). 现在假设你去获取Order实例,这时你还没有去请求Customer或者Employee的数据,所以它们应当是不存在的,对吧? 但是它们也不为Null, 因为Nhibernate 为Customer属性分配了CustomerProxyType的实例,为Employee属性分配了EmployeeProxyType实例。并且还为每一个实例初始化了它们的标识符的值,一般是ID的值。这个标识符的值是在查询order记录的时候获取到的。

你现在可以放心的使用Order实例,你甚至可以访问Employee和Customer的实例,这时什么也不会发生,不会有查询操作。但是,注意,当你去访问代理实例的非标识符成员(也就是属性和方法)的时候,Nhibernate为了确保你请求的非标识符成员有数据,它会去数据库请求数据。在这里就是当你去访问Employee和Customer的属性和方法的时候,Nhibernate会去数据库请求你想要的数据。那Nhibernate是怎么实现的这种机制呢? 因为代理会重写你的实体类(Entity)的属性和方法,当属性和方法被调到的时候,Nhibernate会检查,如果当前实例的数据已经存在了,它就只会调用实体类(Entity)中属性和方法的实现,如果没有数据,它会先去获取数据,然后再调用实体类(Entity)中属性和方法的实现。

这是基本的面相对象思想,你的实体类(Entity)是Nhibernate 代理的基类。 这些代理(proxy)会为你的实体类(Entity)的行为上添加一些额外的行为。 Nhibernate需要重写所有的公共成员(public)来确保在合适的时间这些额外的行为能被触发。