NHibernate官方文档中文版——持久化类(Persistent Classes)
持久化类是一个应用程序中的类,主要用来实现业务逻辑(例如,在电商应用中的客户和订单类)。持久化类,就像它的名字一样,生命周期短暂并且用来持久化的据库对象实例。
如果这些类的构造能够依照一些简单的原则,比如说Plain Old CLR Object (POCO)编程模型,NHibernate能够工作得最好。
一个简单的POCO例子
大多数的.NET应用程序需要一个持久化类来表示猫科动物。
using System; using System.Collections.Generic; namespace Eg { public class Cat { long id; // 标识符 public virtual long Id { get { return id; } protected set { id = value; } } public virtual string Name { get; set; } public virtual Cat Mate { get; set; } public virtual DateTime Birthdate { get; set; } public virtual float Weight { get; set; } public virtual Color Color { get; set; } public virtual ISet<Cat> Kittens { get; set; } public virtual char Sex { get; set; } // AddKitten 方法并不是NHibernate必须的 public virtual void AddKitten(Cat kitten) { kittens.Add(kitten); } }
这里主要有四个原则:
为持久化字段声明属性
Cat为它所有的持久化字段都声明了属性。很多其他的ORM工具直接持久化实体变量。我们相信将实现细节和持久化机制解耦是一个很好的选择。NHibernate持久化属性,使用的是他们的getter 和setter方法。
属性不一定要被声明称public,NHibernate支持 internal, protected, protected internal 或者 private等可见性属性的持久化。
就像例子中展示的那样,NHibernate支持自动属性和后台字段。
实现一个默认的构造函数
Cat有一个隐式的默认(无参)构造函数。所有持久化泪都必须有一个默认的构造函数(可能是非公共的),因此NHibernate 可以使用Activator.CreateInstance()来实例化他们。
提供一个标识符属性(可选)
Cat有一个属性叫做Id。这个属性是数据表中这条数据的主键信息。这个属性也可以叫其他名称,并且它的类型可能是任何基础类型,string或者System.DateTime(如果你的数据表有混合主键,你甚至可以使用一个用户自定义类作为主键——参看下文的混合主键)
主键属性是可选的。你可以忽略它让NHibernate用其内部的主键机制来追踪它。然而,对于大多数应用程序来说,使用主键仍然是一个很好的(并且非常普遍的)设计策略。
而且,一些功能只有在声明了标识符属性之后才能够使用:
- 级联更新(参见“生命周期对象”)
ISession.SaveOrUpdate()
我们推荐你在持久化类中声明命名风格一致的主键。
最好是非封闭的类和虚方法(可选)
NHibernate的核心功能,代理,依赖于持久化类的非封闭特性和它所有的公共的虚方法,虚属性和虚事件。其他可能的实现核心功能的方式是通过继承接口来声明所有的公共成员。
你可以持久化不实现接口和不包含任何虚成员的封闭类,但是你不能使用代理——这样会限制你性能调优。
实现继承
子类也必须满足上面的第一和第二条规则。它从父类Cat继承了标识符属性。
using System; namespace Eg { public class DomesticCat : Cat { public virtual string Name { get; set; } } }
实现Equals() 和 GetHashCode()
如果你想要合并持久化类对象(例如,在一个ISet<T>中),你必须重写Equals() and GetHashCode() 方法。
这仅会在这些对象在两个不同的ISession中加载的时候应用,因为NHibernate仅仅保证在一个ISession范围中唯一(a == b ,默认的Equals()实现)。
尽管两个对象a和b对应相同的数据列(他们有相同的主键作为他们的标识符),我们也不能保证他们在特定的ISession上下文之外还是相同的对象实体。
最明显的实现Equals()/GetHashCode() 的方式是通过比较两个对象的标识符值。如果他们的值是相同的,说明两个对象都对应同一个数据列,因此他们就是相同的(如果他们都被添加到了一个ISet<T>中, 只能有一个存在)。不幸的是,我们不能用这种方法,因为NHibernate只会把标识符值赋给持久化对象实例,新生成实体不会有标识符值。我们建议通过使用业务主键来实现Equals() and GetHashCode() 。
业务主键相同也意味着Equals() 方法仅仅比较业务主键,一个在我们现实世界里用来比较实体的键。
public class Cat { ... public override bool Equals(object other) { if (this == other) return true; Cat cat = other as Cat; if (cat == null) return false; // null or not a cat if (Name != cat.Name) return false; if (!Birthday.Equals(cat.Birthday)) return false; return true; } public override int GetHashCode() { unchecked { int result; result = Name.GetHashCode(); result = 29 * result + Birthday.GetHashCode(); return result; } } }
记住,我们的候选键(在这个例子中是名字和生日的混合主键)必须在特定的比较操作中合理(也许甚至在一个单独的使用场景中)。我们不需要一个我们通常应用在主键上面的固定的标准。
Keep in mind that our candidate key (in this case a composite of name and birthday) has to be only valid for a particular comparison operation (maybe even only in a single use case). We don't need the stability criteria we usually apply to a real primary key!
动态类
需要注意的是,下面的特性只是实验性的功能,可能会在以后的版本中改变。
持久化实体不一定必须以POCO的方式展在运行时展现。NHibernate也支持动态类模型(运行时使用Dictionarys 的Dictionaries )。通过这种方法,你不需要编写实体化类,只要写mapping文件就行了。
NHibernate默认使用一般POCO模式。你可以使用default_entity_mode 配置选项(参见 Table 3.2, “NHibernate Configuration Properties”)来为一个特定的ISessionFactory设置一个默认实体展示模型。
下面的例子展示了使用Maps(Dictionary)来设置实体展示模型。 首先,在mapping文件中,必须声明一个entity-name 而不是类名(或者是类名的一种补充)。
<hibernate-mapping> <class entity-name="Customer"> <id name="id" type="long" column="ID"> <generator class="sequence"/> </id> <property name="name" column="NAME" type="string"/> <property name="address" column="ADDRESS" type="string"/> <many-to-one name="organization" column="ORGANIZATION_ID" class="Organization"/> <bag name="orders" inverse="true" lazy="false" cascade="all"> <key column="CUSTOMER_ID"/> <one-to-many class="Order"/> </bag> </class> </hibernate-mapping>
Note that even though associations are declared using target class names, the target type of an associations may also be a dynamic entity instead of a POCO.
需要注意的是,尽管使用目标类名称来声明了关联,关联的目标类型也可能是dynamic实体而不是POCO。
After setting the default entity mode to dynamic-map for the ISessionFactory, we can at runtime work with Dictionaries of Dictionaries:
在将ISessionFactory默认实体类型模式设置成dynamic-map ,我们可以在运行时使用Dictionarys 的Dictionaries :
using(ISession s = OpenSession()) using(ITransaction tx = s.BeginTransaction()) { // Create a customer var frank = new Dictionary<string, object>(); frank["name"] = "Frank"; // Create an organization var foobar = new Dictionary<string, object>(); foobar["name"] = "Foobar Inc."; // Link both frank["organization"] = foobar; // Save both s.Save("Customer", frank); s.Save("Organization", foobar); tx.Commit(); }
动态mapping的优点是不需要实体类型。然而,你失去了编译时类型检查的功能,这将会导致在运行时会遇到很多异常。由于NHibernate mapping,数据库对象结构能能够被简单地
实体展示模型可以在每个ISession 中设置。
The advantages of a dynamic mapping are quick turnaround time for prototyping without the need for entity class implementation. However, you lose compile-time type checking and will very likely deal with many exceptions at runtime. Thanks to the NHibernate mapping, the database schema can easily be normalized and sound, allowing to add a proper domain model implementation on top later on.
Entity representation modes can also be set on a per ISession basis:
using (ISession dynamicSession = pocoSession.GetSession(EntityMode.Map)) { // Create a customer var frank = new Dictionary<string, object>(); frank["name"] = "Frank"; dynamicSession.Save("Customer", frank); ... } // Continue on pocoSession
请注意使用一个EntityMode 调用GetSession() 是ISession 的API而不是在ISessionFactory。
Please note that the call to GetSession() using an EntityMode is on the ISession API, not the ISessionFactory. That way, the new ISession shares the underlying ADO connection, transaction, and other context information. This means you don't have tocall Flush() and Close() on the secondary ISession, and also leave the transaction and connection handling to the primary unit of work.
Tuplizers
NHibernate.Tuple.Tuplizer, and its sub-interfaces, are responsible for managing a particular representation of a piece of data, given that representation's NHibernate.EntityMode. If a given piece of data is thought of as a data structure, then a tuplizer is the thing which knows how to create such a data structure and how to extract values from and inject values into such a data structure. For example, for the POCO entity mode, the correpsonding tuplizer knows how create the POCO through its constructor and how to access the POCO properties using the defined property accessors. There are two high-level types of Tuplizers, represented by the NHibernate.Tuple.Entity.IEntityTuplizer and NHibernate.Tuple.Component.IComponentTuplizer interfaces. IEntityTuplizers are responsible for man aging the above mentioned contracts in regards to entities, while IComponentTuplizers do the same for components.
Users may also plug in their own tuplizers. Perhaps you require that a System.Collections.IDictionary implementation other than System.Collections.Hashtable be used while in the dynamic-map entity-mode; or perhaps you need to define a different proxy generation strategy than the one used by default. Both would be achieved by defining a custom tuplizer implementation. Tuplizers definitions are attached to the entity or component mapping they are meant to manage. Going back to the example of our customer entity:
<hibernate-mapping> <class entity-name="Customer"> <!-- Override the dynamic-map entity-mode tuplizer for the customer entity --> <tuplizer entity-mode="dynamic-map" class="CustomMapTuplizerImpl"/> <id name="id" type="long" column="ID"> <generator class="sequence"/> </id> <!-- other properties --> ... </class> </hibernate-mapping> public class CustomMapTuplizerImpl : NHibernate.Tuple.Entity.DynamicMapEntityTuplizer { // override the BuildInstantiator() method to plug in our custom map... protected override IInstantiator BuildInstantiator(NHibernate.Mapping.PersistentClass mappingInfo) { return new CustomMapInstantiator(mappingInfo); } private sealed class CustomMapInstantiator : NHibernate.Tuple.DynamicMapInstantiator { // override the generateMap() method to return our custom map... protected override IDictionary GenerateMap() { return new CustomMap(); } } }
生命周期回调方法
一个持久化类可以实现ILifecycle 接口,这个接口提供了一些回调方法。这些回调方法能够让持久化类在“保存/加载”之前或者“删除/更新”之后执行必要的初始化/清理操作。
然而,Nhibernate的IInterceptor 接口提供了其他较为独立的方法。
public interface ILifecycle { (1) LifecycleVeto OnSave(ISession s); (2) LifecycleVeto OnUpdate(ISession s); (3) LifecycleVeto OnDelete(ISession s); (4) void OnLoad(ISession s, object id); }
(1)OnSave - 只在对象保存或者插入之前调用
(2)OnUpdate - 只在对象更新之前调用(当对象传入到ISession.Update()方法的时候)
(3)OnDelete - 只在对象删除之前调用
(4)OnLoad - 只在对象加载之之后调用
OnSave(), OnDelete() 和OnUpdate()方法可能用来进行级联保存和删除独立对象。这是一个在mapping文件中声明级联操作的另一种方法。OnLoad() 可以被用来从持久化态初始化到瞬时态。它可能不会被用来加载独立对象,因为ISession 接口不会从这个方法内部被调用。OnLoad(), OnSave() 和OnUpdate()的其他作用就是用来把引用储存到 当前的ISession以备后续使用。
需要注意的是,OnUpdate() 并不是对象的持久态更新的时候都会被调用。它仅仅在一个瞬时态的实体被传递到ISession.Update()方法的时候被调用。
如果OnSave(), OnUpdate() or OnDelete() 方法返回了LifecycleVeto.Veto,相应的操作就被静默地拒绝执行了。如果CallbackException 异常被抛出,操作被拒绝执行并且这个异常会被抛到应用程序中。
需要注意的是,OnSave() 在一个标识符被赋予到一个对象的时候被调用,除非本地的键生成器被占用(except when native key generation is used.)。
IValidatable接口回调
如果持久化类需要在持久化状态之前检查变量,它可能会应用下面的接口:
public interface IValidatable { void Validate(); }
如果一个变量不合理的时候,对象会抛出一个ValidationFailure 异常。在Validate()方法内部,一个Validatable 实例不会改变它的状态。
和ILifecycle 接口的回调方法不一样,Validate() 可能被调用多次,而这个次数是无法预知的。因此,应用程序不能通过依赖调用Validate() 方法来实现业务功能。