第1章 NHibernate体系结构
总览
对NHibernate体系结构的非常高层的概览:
这幅图展示了NHibernate使用数据库和配置文件数据来为应用程序提供持久化服务(和持久化的对象)。
我们试图显示更多NHibernate运行时体系结构的细节。 但挺不幸的,NHibernate是比较灵活的并且提供了好几种不同的运行方式。我们展示一下两种极端情况。轻型体系中,应用程序自己提供ADO.NET连接,并且自行管理事务。这种方式使用了NHibernate API的一个最小子集。
全面解决体系中,对于应用程序来说,所有的底层ADO.NET API都被抽象了,NHibernate会替你照管所有的细节。
下面是图中一些对象的定义:
SessionFactory (NHibernate.ISessionFactory) |
对属于单一数据库的编译过的映射文件的一个线程安全的,不可变的缓存快照。它是Session的工厂,是ConnectionProvider的客户。可以持有一个可选的(第二级)数据缓存,可以在进程级别或集群级别保存可以在事物中重用的数据。 |
会话Session (NHibernate.ISession) |
单线程,生命期短促的对象,代表应用程序和持久化层之间的一次对话。封装了一个ADO.NET连接。也是Transaction的工厂。保存有必需的(第一级)持久化对象的缓存,用于遍历对象图,或者通过标识符查找对象。 |
持久化对象(Persistent)及其集合(Collections) |
生命期短促的单线程的对象,包含了持久化状态和商业功能。它们可能是普通的对象,唯一特别的是他们现在从属于且仅从属于一个Session。一旦Session被关闭,他们都将从Session中取消联系,可以在任何程序层*使用(比如,直接作为传送到表现层的DTO,数据传输对象)。 |
临时对象(Transient Object)及其集合(Collection) |
目前没有从属于一个Session的持久化类的实例。他们可能是刚刚被程序实例化,还没有来得及被持久化,或者是被一个已经关闭的Session所实例化的。 |
事务Transaction (NHibernate.ITransaction) |
(可选) 单线程,生命期短促的对象,应用程序用它来表示一批工作的原子操作。是底层的ADO.NET事务的抽象。一个Session某些情况下可能跨越多个Transaction 事务。 |
ConnectionProvider (NHibernate.Connection.ConnectionProvider) |
(可选)ADO.NET连接的工厂。从底层的IDbConnection抽象而来。对应用程序不可见,但可以被开发者扩展/实现。 |
TransactionFactory (net.sf.hibernate.TransactionFactory) |
(可选)事务实例的工厂。对应用程序不可见,但可以被开发者扩展/实现。 |
在上面的轻型结构中,程序没有使用Transaction / TransactionFactory 或ConnectionProvider API,直接和ADO.NET对话了。
第2章 ISessionFactory配置
目录
可编程配置方式
获取ISessionFactory
用户自行提供ADO.NET连接
NHibernate提供ADO.NET连接
可选配置属性
SQL 方言 (SQL Dialects)
外连接抓取(Outer Join Fetching )
自定义 CacheProvider
查询语言替换
Logging
因为NHibernate被设计为可以在许多不同环境下工作,所以它有很多配置参数。幸运的是,大部分都已经有默认值了。 NHibernate.Test.dll包含了一个示例的配置文件app.config,它演示了一些可变的参数。
可编程配置方式
NHibernate.Cfg.Configuration的一个实例代表了应用程序中所有的.NET类到SQL数据库的映射的集合。Configuration用于构造一个(不可变的)ISessionFactory。这些映射是从一些XML映射文件中编译得来的。
你可以得到一个Configuration的实例,直接实例化它即可。下面有一个例子,用来从两个XML配置文件(和exe文件在同一个目录下)中的映射中初始化:
Configuration cfg = new Configuration()
.AddXmlFile("Item.hbm.xml")
.AddXmlFile("Bid.hbm.xml");
另外一个(某些时候更好的)方法是让NHibernate自行用GetManifestResourceStream()来装载映射文件
Configuration cfg = new Configuration()
.AddClass( typeof(NHibernate.Auction.Item) )
.AddClass( typeof(NHibernate.Auction.Bid) );
NHibernate 就会在这些类型的程序集的嵌入的资源中寻找叫做NHibernate.Auction.Item.hbm.xml 和NHibernate.Auction.Bid.hbm.xml 的映射文件。这种方法取消了所有对文件名的硬编码。
另外一个(可能是最好的)方法是让NHibernate读取一个程序集中所有的配置文件:
Configuration cfg = new Configuration()
.AddAssembly( "NHibernate.Auction" );
NHibernate将会遍历程序集查找任何以hbm.xml结尾的文件。 这种方法取消了所有对文件名的硬编码并且确保程序集中的配置文件文件都会被加载。
在使用VisualStudio.NET或者NAnt生成程序集时请确保hbm.xml文件是作为嵌入资源(Embedded Resources)添加的。
Configuration也可以指定一些可选的配置项
Hashtable props = new Hashtable();
...
Configuration cfg = new Configuration()
.AddClass( typeof(NHibernate.Auction.Item) )
.AddClass( typeof(NHibernate.Auction.Bid) );
cfg.Properties = props;
Configuration是仅在配置期使用的对象,从第一个SessionFactory开始建立的时候,它就失效了。
获取ISessionFactory
当所有的映射都被Configuration解析之后,应用程序为了得到ISession实例,必须先得到它的工厂。这个工厂应该是被应用程序的所有线程共享的:ISessionFactory sessions = cfg.BuildSessionFactory();
当然,NHibernate并不禁止你的程序实例化多个ISessionFactory。在你使用不止一个数据库的时候,这就有用了。
用户自行提供ADO.NET连接
ISessionFactory可以使用一个用户自行提供的ADO.NET连接来打开一个ISession。这种设计可以让应用程序来自己管理ADO.NET连接:
IDbConnection conn = myapp.GetOpenConnection();
ISession session = sessions.OpenSession(conn);
// do some data access work
应用程序必须小心,不能在同一个连接上打开两个并行的 ISession!
NHibernate提供ADO.NET连接
另一种方法就是,你可以让ISessionFactory替你打开连接。SessionFactory必须事先知道ADO.NET连接的参数,有几种不同的方法设置参数:
1. 通过提供一个IDictionary实例给Configuration.Properties。
2. 在名为nhibernate的System.Configuration.NameValueSectionHandler类型的配置节点添加属性。
3. 在hibernate.cfg.xml 中包含<property>元素。
如果你使用这种方法,打开一个ISession是非常简单的:
ISession session = sessions.OpenSession(); // open a new Session
// do some data access work, an ADO connection will be used on demand
所有的NHibernate属性名和约束都在 NHibernate.Cfg.Environment类中定义。我们讨论一下ADO.NET连接配置最重要的几项设置:
假若你设置了如下的属性,Hibernate会使用ADO.NET Data Provider来得到连接:
表 2.1. NHibernate ADO.NET 属性
属性名 |
用途 |
hibernate.connection.provider_class |
定制IConnectionProvider的类型. 例如:full.classname.of.ConnectionProvider (如果提供者创建在NHibernate中), 或者 full.classname.of.ConnectionProvider, assembly (如果使用一个自定义的IConnectionProvider接口的实现,它不属于NHibernate)。 |
hibernate.connection.driver_class |
定制IDriver的类型. full.classname.of.Driver (如果驱动类创建在NHibernate中), 或者 full.classname.of.Driver, assembly (如果使用一个自定义IDriver接口的实现,它不属于NHibernate)。 |
hibernate.connection.connection_string |
用来获得连接的连接字符串. |
hibernate.connection.isolation |
设置事务隔离级别. 请检查 System.Data.IsolationLevel 来得到取值的具体意义并且查看数据库文档以确保级别是被支持的。 例如: Chaos, ReadCommitted, ReadUncommitted, RepeatableRead, Serializable, Unspecified |
下面是一个在web.config文件中指定连接属性的例子:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System,
Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<nhibernate>
<add
key="hibernate.connection.provider"
value="NHibernate.Connection.DriverConnectionProvider"
/>
<add
key="hibernate.connection.driver_class"
value="NHibernate.Driver.SqlClientDriver"
/>
<add
key="hibernate.connection.connection_string"
value="Server=127.0.0.1;Initial Catalog=thedatabase;Integrated Security=SSPI"
/>
<add
key="hibernate.connection.isolation"
value="ReadCommitted"
/>
<add
key="hibernate.dialect"
value="NHibernate.Dialect.MsSql2000Dialect"
/>
</nhibernate>
<!-- log4net (required by NHibernate) and other app specific config follows -->
</configuration>
可选配置属性
下面是一些在运行时可以改变NHibernate行为的其他配置。所有这些都是可选的,也有合理的默认值。
表 2.2. NHibernate 配置属性
属性名 |
用途 |
hibernate.dialect |
NHibernate方言(Dialect)的类名 - 可以让NHibernate使用某些特定的数据库平台的特性 例如: full.classname.of.Dialect(如果方言创建在NHibernate中), 或者full.classname.of.Dialect, assembly (如果使用一个自定义的方言的实现,它不属于NHibernate)。 |
hibernate.default_schema |
在生成的SQL中,scheml/tablespace的全限定名. 例如: SCHEMA_NAME |
hibernate.prepare_sql |
是否准备sql语句 例如: true | false |
hibernate.session_factory_name |
SessionFactory被创建后将自动绑定这个名称. 例如: some.name |
hibernate.use_outer_join |
允许使用外连接抓取。 例如:true | false |
hibernate.cache.provider_class |
指定一个自定义的CacheProvider缓存提供者的类名 例如: full.classname.of.CacheProvider(如果ICacheProvider创建在NHibernate中), 或full.classname.of.CacheProvider, assembly(如果使用一个自定义的ICacheProvider,它不属于NHibernate)。 |
hibernate.query.substitutions |
把NHibernate查询中的一些短语替换为SQL短语(比如说短语可能是函数或者字符)。 例如: hqlLiteral=SQL_LITERAL, hqlFunction=SQLFUNC |
SQL 方言 (SQL Dialects)
你总是可以为你的数据库设置一个hibernate.dialect方言,它是NHibernate.Dialect.Dialect 的一个子类。如果你不需要使用基于native或者sequence的主键自动生成算法,或者悲观锁定(使用ISession.Lock() 或者 IQuery.SetLockMode())的话,方言就可以不必指定。然而,假若你指定了一个方言,Hibernate会为上面列出的一些属性使用特殊默认值,省得你手工指定它们。
表 2.3. NHibernate SQL 方言 (hibernate.dialect)
RDBMS |
方言 |
DB2 |
NHibernate.Dialect.DB2Dialect |
PostgreSQL |
NHibernate.Dialect.PostgreSQLDialect |
MySQL |
NHibernate.Dialect.MySQLDialect |
Oracle (any version) |
NHibernate.Dialect.OracleDialect |
Oracle 9/10g |
NHibernate.Dialect.Oracle9Dialect |
Sybase |
NHibernate.Dialect.SybaseDialect |
Microsoft SQL Server 2000 |
NHibernate.Dialect.MsSql2000Dialect |
Microsoft SQL Server 7 |
NHibernate.Dialect.MsSql7Dialect |
Firebird |
NHibernate.Dialect.FirebirdDialect |
外连接抓取(Outer Join Fetching )
如果你的数据库支持ANSI或者Oracle风格的外连接,外连接抓取可能提高性能,因为可以限制和数据库交互的数量(代价是数据库自身进行了更多的工作)。外连接抓取允许你在一个SELECT语句中就可以得到一个由多对一或者一对一连接构成的对象图。
默认情况下,抓取在叶对象,拥有代理的对象或者产生对自身的引用时终止。
对一个特定关联来说,通过在XML映射文件中设置outer-join属性可以控制是否开启抓取功能。
设置hibernate.use_outer_join为false将禁用全局的外连接抓取.设置为true将启用所有一对一(one-to-one)和多对一(many-to-one)关联中的外连接抓取,默认情况下,它被设置为auto,即自动外连接。但是,一对多关联和集合永远不会使用外连接抓取,除非对每个特定的关联进行明确声明。这一行为可以在运行时通过NHibernate 查询重载。
自定义 CacheProvider
通过实现NHibernate.Cache.ICacheProvider接口,你可以整合一个第二级缓存进来。你可以通过hibernate.cache.provider_class选择某个自定义的实现。
查询语言替换
你可以使用hibernate.query.substitutions定义新的NHibernate查询短语。比如说:
hibernate.query.substitutions true=1, false=0
会在生成的SQL中把短语true和 false替换成整数值。
hibernate.query.substitutions toLowercase=LOWER
这可以让你重新命名SQL的LOWER 函数。
Logging
通过Apache log4net,NHibernate记录很多事件。
你可以从 http://logging.apache.org/log4net/下载 log4net. 要使用log4net,你要在app.config或者web.config中配置log4net节点.在src/NHibernate.Test工程中有一个配置的例子.
我们强烈建议你熟悉NHibernate's的log信息。NHibernate's的很多工作都会尽量详细的留下log,也没有让它变的难以阅读。这是用来解决问题的最基本的设施。
第3章 持久化类(Persistent Classes)
目录
POCO 简单示例
为持久化字段声明访问器(getters 和 setters)
实现一个默认的构造方法(constructor)
提供一个标识属性(identifier property)(可选)
建议使用不是sealed的类 (可选)(sealed 未知的,密封的)
实现继承(Inheritance)
实现Equals()和GetHashCode()
持久化生命周期(Lifecycle)中的回调(Callbacks)
合法性检查(Validatable)回调
用属性替代 XML
持久化类是应用程序用来解决商业问题的类(比如,在电子交易程序中的Customer和Order)。持久化类,就如同它的名字暗示的,是短暂存在的,它的实例会被持久性保存于数据库中。
如果这些类符合简单的规则,NHibernate能够工作得最好,这些规则就是Plain Old CLR Object (POJO,简单传统CLR对象)编程模型。
POCO 简单示例
用一个类描述一只猫。
public class Cat
{
private long _id; // identifier
private string _name;
private DateTime _birthdate;
private Cat _mate;
private Set _kittens;
private Color _color;
private char _sex;
private float _weight;
public long Id
{
get { return _id; }
set { _id = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public DateTime Birthdate
{
get { return _birthdate; }
set { _birthdate = value; }
}
public Cat Mate
{
get { return _mate; }
set { _mate = value; }
}
public Set Kittens
{
get { return _kittens; }
set { _kittens = value; }
}
public Color Color
{
get { return _color; }
set { _color = value; }
}
public char Sex
{
get { return _sex; }
set { _sex = value; }
}
public float Weight
{
get { return _weight; }
set { _weight = value; }
}
}
有四条主要的规则:
为持久化字段声明访问器(getters 和 setters)
Cat为它的所有可持久化字段声明了getters 和 setters访问器。用访问器来替代直接访问字段是个好习惯。然而也可以通过字段(field)来使用NNHibernate。
属性不一定需要声明为public的。NHibernate可以对default,procted, internal, or private的属性一视同仁地执行持久化。
实现一个默认的构造方法(constructor)
Cat有一个显式的无参数默认构造方法。所有的持久化类都必须具有一个默认的构造方法(可以不是public的),这样的话NHibernate就可以使用Constructor.Invoke()来实例化它们。
提供一个标识属性(identifier property)(可选)
建议使用不是sealed的类 (可选)
实现继承(Inheritance)
实现Equals()和GetHashCode()
持久化生命周期(Lifecycle)中的回调(Callbacks)
合法性检查(Validatable)回调
用属性替代 XML
第4章 O/R Mapping基础
目录
映射声明(Mapping declaration)
Schema
hibernate-mapping
class
id
联合ID(composite-id)
识别器(discriminator)
版本(version)(可选)
时间戳(timestamp )(可选)
property
多对一(many-to-one)
一对一(one-to-one)
组件(component)
子类(subclass)
连接的子类(joined-subclass)
map, set, list, bag
引用(import)
NHibernate的类型
实体(Entities)和值(values)
基本值类型
自定义值类型
映射到"任意"(any)类型
SQL中引号包围的标识符
映射文件的模块化(Modular mapping files)
映射声明(Mapping declaration)
对象和关系数据库之间的映射是用一个XML文档(XML document)来定义的。这个映射文档被设计为易读的,并且可以手工修改。映射语言是以.NET为中心的,意味着映射是按照持久化类的定义来创建的,而非表的定义。
请注意,虽然很多Hibernate用户选择手工定义XML映射文档,也有一些工具来生成映射文档,包括XDoclet,Middlegen和AndroMDA. (译者注:这里是NHibernate文档中一处没有从Hibernate文档中转换过来的部分,NHibernate中并没有像XDoclet,Middlegen和AndroMDA这样的工具,我一般会采用MyGeneration这样的代码生成工具来生成XML配置文档。)
让我们从一个映射的例子开始:
<?xml version="1.0" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0"
namespace="Eg" assembly="Eg">
<class name="Cat" table="CATS" discriminator-value="C">
<id name="Id" column="uid" type="Int64">
<generator class="hilo"/>
</id>
<discriminator column="subclass" type="Char"/>
<property name="Birthdate" type="Date"/>
<property name="Color" not-null="true"/>
<property name="Sex" not-null="true" update="false"/>
<property name="Weight"/>
<many-to-one name="Mate" column="mate_id"/>
<set name="Kittens">
<key column="mother_id"/>
<one-to-many class="Cat"/>
</set>
<subclass name="DomesticCat" discriminator-value="D">
<property name="Name" type="String"/>
</subclass>
</class>
<class name="Dog">
<!-- mapping for Dog could go here -->
</class>
</hibernate-mapping>
我们现在开始讨论映射文档的内容。我们只描述NHibernate在运行时用到的文档元素和属性。映射文档还包括一些额外的可选属性和元素,它们在使用schema导出工具的时候会影响导出的数据库schema结果。(比如, not-null 属性。)
Schema
所有的XML映射都需要使用nhibernate-mapping-2.0 schema。目前的schema可以在NHibernate的资源路径或者是NHibernate.dll的嵌入资源(Embedded Resource)中找到。NHibernate总是会优先使用嵌入在资源中的schema文件。
在使用VisualStudio.NET时,你应该将hibernate-mapping拷贝到C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Packages\schemas\xml路径中,以获得智能感知功能。
hibernate-mapping
这个元素包括四个可选的属性。schema属性,指明了这个映射所引用的表所在的schema名称。假若指定了这个属性,表名会加上所指定的schema的名字扩展为全限定名。假若没有指定,表名就不会使用全限定名。default-cascade指定了未明确注明cascade属性的属性和集合类会采取什么样的默认级联风格。auto-import属性默认让我们在查询语言中可以使用非全限定名的类名。default-access告诉我们怎么访问属性值。
<hibernate-mapping
schema="schemaName" (1)
default-cascade="none|save-update" (2)
auto-import="true|false" (3)
default-access="property|field|nosetter|ClassName" (4)
assembly="assembly.name" (5)
namespace="namespace.name" (6)
>
href="#hm1-co" (1) |
schema (可选): 数据库schema名称. |
href="#hm2-co" (2) |
default-cascade (可选 - 默认为 none): 默认的级联风格. |
href="#hm3-co" (3) |
auto-import (可选 - 默认为 true): 指定是否我们可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。 |
href="#hm4-co" (4) |
default-access (可选 - 默认为 property): NHibernate访问属性值时的策略。 |
href="#hm5-co" (5) |
assembly (可选): 指定一个程序集,如果在映射文档中没有指定程序集,就使用这个程序集。 |
href="#hm6-co" (6) |
namespace (可选): 指定一个命名空间前缀,如果在映射文档中没有指定全限定名,就使用这个命名空间名。 |
假若你有两个持久化类,它们的非全限定名是一样的(就是在不同的命名空间里面--译者注),你应该设置auto-import="false"。假若说你把一个“import过”的名字同时对应两个类, NHibernate会抛出一个异常。
class
你可以使用class元素来定义一个持久化类:
<class
name="ClassName" (1)
table="tableName"(2)
discriminator-value="discriminator_value"(3)
mutable="true|false"(4)
schema="owner"(5)
proxy="ProxyInterface"(6)
dynamic-update="true|false"(7)
dynamic-insert="true|false"(8)
polymorphism="implicit|explicit"(9)
where="arbitrary sql where condition"(10)
persister="PersisterClass"(11)
lazy="true|false"(12)
/>
href="#class1-co" (1) |
name: 持久化类(或者接口)的全限定名。 |
href="#class2-co" (2) |
table: 对应的数据库表名。 |
href="#class3-co" (3) |
discriminator-value (可选 - 默认和类名一样): 一个用于区分不同的子类的值,在多态行为时使用。 |
href="#class4-co" (4) |
mutable (可选, 默认为 true): 表明该类的实例可变(不可变)。 |
href="#class5-co" (5) |
schema (可选): 覆盖在根<hibernate-mapping> 元素中指定的schema名字。 |
href="#class6-co" (6) |
proxy (可选): 指定一个接口,在延迟装载时作为代理使用。你可以在这里使用该类自己的名字。 |
href="#class7-co" (7) |
dynamic-update (可选, 默认为 false): 指定用于UPDATE 的SQL将会在运行时动态生成,并且只更新那些改变过的字段。 |
href="#class8-co" (8) |
dynamic-insert (可选, 默认为 false): 指定用于INSERT的 SQL 将会在运行时动态生成,并且只包含那些非空值字段。 |
href="#class9-co" (9) |
polymorphism (可选, 默认为 implicit(隐式)): 界定是隐式还是显式的使用查询多态。 |
href="#class10-co" (10) |
where (可选) 指定一个附加的SQL WHERE 条件,在抓取这个类的对象时会一直增加这个条件。 |
href="#class11-co" (11) |
persister (可选): 指定一个定制的 IClassPersister. |
href="#class12-co" (12) |
lazy(可选):假若设置 lazy="true",就是设置这个类自己的名字作为proxy接口的一种等价快捷形式。 |
若指明的持久化类实际上是一个接口,也可以被完美地接受。其后你可以用 <subclass> 来指定该接口的实际实现类名。你可以持久化任何static(静态的)内部类。记得应该使用标准的类名格式,就是说比如:Eg.Foo+Bar
不可变类,mutable="false"不可以被应用程序更新或者删除。这可以让NHibernate做一些小小的性能优化。
可选的proxy属性可以允许延迟加载类的持久化实例。NHibernate开始会返回实现了这个命名接口或者子类(通过的Castle.DynamicProxy)。当代理的某个方法被实际调用的时候,真实的持久化对象才会被装载。参见下面的“用于延迟装载的代理”。
Implicit (隐式)的多态是指,如果查询中给出的是任何超类、该类实现的接口或者该类的名字,都会返回这个类的实例;如果查询中给出的是子类的名字,则会返回子类的实例。Explicit (显式)的多态是指,只有在查询中给出的明确是该类的名字时才会返回这个类的实例;同时只有当在这个 <class> 的定义中作为 <subclass> 或者 <joined-subclass> 出现的子类,才会可能返回。 大多数情况下,默认的polymorphism="implicit"都是合适的。 显式的多态在有两个不同的类映射到同一个表的时候很有用。(允许一个“轻型”的类,只包含部分表字段)。
persister属性可以让你定制这个类使用的持久化策略。你可以指定你自己实现的NHibernate.Persister.EntityPersister的子类,你甚至可以完全从头开始编写一个NHibernate.Persister.IClassPersister接口的实现,可能是用储存过程调用、序列化到文件或者LDAP数据库来实现的。参阅NHibernate.DomainModel.CustomPersister,这是一个简单的例子(“持久化”到一个Hashtable)。
请注意dynamic-update和dynamic-insert的设置并不会继承到子类,所以在<subclass>或者<joined-subclass>元素中可能需要再次设置。这些设置是否能够提高效率要视情形而定。请用你的智慧决定是否使用。
id
被映射的类必须声明对应数据库表主键字段。大多数类有一个属性,为每一个实例包含唯一的标识。 <id> 元素定义了该属性到数据库表主键字段的映射。
<id
name="propertyName" (1)
type="typename" (2)
column="column_name" (3)
unsaved-value="any|none|null|id_value" (4)
access="field|property|nosetter|ClassName"> (5)
<generator class="generatorClass"/>
</id>
href="#id1-co" (1) |
name (可选): 标识属性的名字。 |
href="#id2-co" (2) |
type (可选): 标识NHibernate类型的名字。 |
href="#id3-co" (3) |
column (可选 - 默认为属性名): 主键字段的名字。 |
href="#id4-co" (4) |
unsaved-value (可选 - 默认为 null): 一个特定的标识属性值,用来标志该实例是刚刚创建的,尚未保存。这可以把这种实例和从以前的session中装载过(可能又做过修改--译者注)但未再次持久化的实例区分开来。 |
href="#id5-co" (5) |
access (可选 - 默认为 property): NHibernate用来访问属性值的策略。 |
如果name属性不存在,会认为这个类没有标识属性。
unsaved-value 属性很重要!如果你的类的标识属性不是默认为null的,你应该指定正确的默认值。特别重要的是在使用值类型System.ValueType例如System.Int32 或者 System.Guid作为你的<id>属性时确保清楚的设置这个属性,因为System.ValueType对象不可能为null值。
还有一个另外的<composite-id>声明可以访问旧式的多主键数据。我们强烈不鼓励使用这种方式。
generator
必须声明的 <generator> 子元素是一个.NET类的名字,用来为该持久化类的实例生成唯一的标识。如果这个生成器实例需要某些配置值或者初始化参数,用 <param>元素来传递。
<id name="Id" type="Int64" column="uid" unsaved-value="0">
<generator class="NHibernate.Id.TableHiLoGenerator">
<param name="table">uid_table</param>
<param name="column">next_hi_value_column</param>
</generator>
</id>
所有的生成器都实现NHibernate.Id.IdentifierGenerator接口。这是一个非常简单的接口;某些应用程序可以选择提供他们自己特定的实现。当然,NHibernate提供了很多内置的实现。下面是一些内置生成器的快捷名字:
identity |
对DB2,MySQL, MS SQL Server, Sybase和HypersonicSQL的内置标识字段提供支持。返回的标识符是 Int64, Int32 或者 Int16类型的。 |
sequence(序列) |
对DB2,MySQL, PostgreSQL, Oracle的内置标识字段提供支持。返回的标识符是Int64 Int32 或者 Int16类型的。 |
hilo(高低位) |
使用一个高/低位算法来高效的生成Int64, Int32 或者 Int16类型的标识符。给定一个表和字段(默认分别是hibernate_unique_key 和next)作为高位值得来源。高/低位算法生成的标识符只在一个特定的数据库中是唯一的。 |
seqhilo(使用序列的高低位) |
使用一个高/低位算法来高效的生成Int64, Int32 或者 Int16类型的标识符,给定一个数据库序列(sequence)的名字。 |
uuid.hex |
用一个System.Guid和它的ToString(string format)方法生成字符串类型的标识符。字符串的长度取决于 format的配置。 |
uuid.string |
用一个新的System.Guid产生一个byte[] ,把它转换成字符串。 |
guid |
用一个新的System.Guid 作为标识符。 |
guid.comb |
用Jimmy Nilsson在文章http://www.informit.com/articles/article.asp?p=25862中描述的算法产生一个新的System.Guid。 |
native(本地) |
根据底层数据库的能力选择 identity, sequence 或者 hilo中的一个。 |
assigned(程序设置) |
让应用程序在save()之前为对象分配一个标示符。 |
foreign(外部引用) |
使用另外一个相关联的对象的标识符。和<one-to-one>联合一起使用。 |
高/低位算法(Hi/Lo Algorithm)
hilo 和 seqhilo生成器给出了两种hi/lo算法的实现,这是一种很令人满意的标识符生成算法。第一种实现需要一个“特殊”的数据库表来保存下一个可用的“hi”值。第二种实现使用一个Oracle风格的序列(在被支持的情况下)。
<id name="Id" type="Int64" column="cat_id">
<generator class="hilo">
<param name="table">hi_value</param>
<param name="column">next_value</param>
<param name="max_lo">100</param>
</generator>
</id>
<id name="Id" type="Int64" column="cat_id">
<generator class="seqhilo">
<param name="sequence">hi_value</param>
<param name="max_lo">100</param>
</generator>
</id>
很不幸,你在为NHibernate自行提供Connection时无法使用hilo 。Hibernate必须能够在一个新的事务中得到一个"hi"值。
UUID Hex 算法
<id name="Id" type="String" column="cat_id">
<generator class="uuid.hex">
<param name="format">format_value</param>
<param name="seperator">seperator_value</param>
</generator>
</id>
UUID是通过调用Guid.NewGuid().ToString(format)产生的。format值的设置请参考MSDN文档。默认的seperator很少也不应该被改变。format决定是否配置好的seperator 能替换默认的seperator,并提供给自己使用(译者注:此句可能和原文有出入,请参见英文文档)。
UUID String 算法
UUID是通过调用 Guid.NewGuid().ToByteArray() 并且把 byte[]转换成char[],char[] 做为一个16个字符组成的字符串返回。
GUID 算法
guid 标识符通过调用Guid.NewGuid()产生。 为了提升Guids在MS SQL中作为主键,外键和索引的一部分时的性能,可以使用guid.comb。在别的数据库中使用guid.comb的好处是支持非标准的GUID。
标识字段和序列(Identity columns and Sequences)
对于内部支持标识字段的数据库(DB2,MySQL,Sybase,MS SQL),你可以使用identity关键字生成。对于内部支持序列的数据库(DB2,Oracle, PostgreSQL),你可以使用sequence风格的关键字生成。这两种方式对于插入一个新的对象都需要两次SQL查询。当使用MS SQL并且采用identity主键生成器,select SCOPE_IDENTITY()将会被附加到insert的sql语句,因而不可避免的执行两个不同的IDbCommand。
<id name="Id" type="Int64" column="uid">
<generator class="sequence">
<param name="sequence">uid_sequence</param>
</generator>
</id>
<id name="Id" type="Int64" column="uid" unsaved-value="0">
<generator class="identity"/>
</id>
对于跨平台开发,native策略会从identity, sequence 和hilo中进行选择,取决于底层数据库的支持能力。
程序分配的标识符(Assigned Identifiers)
如果你需要应用程序分配一个标示符(而非NHibernate来生成它们),你可以使用assigned生成器。这种特殊的生成器会使用已经分配给对象的标识符属性的标识符值。用这种特性来分配商业行为的关键字要特别小心(基本上总是一种可怕的设计决定)。
因为其继承天性,使用这种生成器策略的实体不能通过ISession的SaveOrUpdate()方法保存。作为替代,你应该明确告知NHibernate是应该被save还是update,分别调用ISession的Save()或Update()方法。
联合ID(composite-id)
<composite-id
name="propertyName"(1)
class="ClassName"(2)
unsaved-value="any|none"(3)
access="field|property|nosetter|ClassName">
<key-property name="propertyName" type="typename" column="column_name"/>
<key-many-to-one name="propertyName class="ClassName" column="column_name"/>
......
</composite-id>
如果表使用联合主键,你可以把类的多个属性组合成为标识符属性。<composite-id>元素接受<key-property>属性映射和<key-many-to-one>属性映射作为子元素。
<composite-id>
<key-property name="medicareNumber"/>
<key-property name="dependent"/>
</composite-id>
你的持久化类必须重载Equals()和HashCode()方法,来实现组合的标识符判断等价.也必须实现Serializable接口
不幸的是,这种组合关键字的方法意味着一个持久化类是它自己的标识。除了对象自己之外,没有什么方便的“把手”可用。你必须自己初始化持久化类的实例,在使用组合关键字Load()持久化状态之前,必须填充他的联合属性。我们会在TODO: LINKTOCOMPENENTS 中说明一种更加方便的方法,把联合标识实现为一个独立的类,下面描述的属性只对这种备用方法有效:
href="#composite-id1-co" (1) |
name (可选): 一个组件类型,持有联合标识(参见下一节)。 |
href="#composite-id2-co" (2) |
class (可选 - 默认为通过反射(reflection)得到的属性类型): 作为联合标识的组件类名(参见下一节)。 |
href="#composite-id3-co" (3) |
unsaved-value (可选 - 默认为 none): 假如被设置为any的值,就表示新创建,尚未被持久化的实例将持有的值。 |
识别器(discriminator)
在"一棵对象继承树对应一个表"的策略中,<discriminator>元素是必需的,它声明了表的识别器字段。识别器字段包含标志值,用于告知持久化层应该为某个特定的行创建哪一个子类的实例。只能使用如下受到限制的一些类型:String, Char, Int32, Byte, Int16, Boolean, YesNo, TrueFalse.
<discriminator
column="discriminator_column"(1)
type="discriminator_type"(2)
force="true|false"(3)
insert="true|false" (4)
/>
href="#discriminator1-co" (1) |
column (可选 - 默认为 class) 识别器字段的名字 |
href="#discriminator2-co" (2) |
type (可选 - 默认为 String) 一个NHibernate字段类型的名字 |
href="#discriminator3-co" (3) |
force (可选 - 默认为 false) "强制"NHibernate指定允许的识别器值,就算取得的所有实例都是根类的。 |
href="#discriminator4-co" (4) |
insert (可选 - 默认为 true) 当识别器是被映射的组件的标识符的一部分时设置为false。 |
标识器字段的实际值是根据<class> 和<subclass>元素的discriminator-value得来%
(缺了很多)
第5章 集合类(Collections)映射
目录
持久化集合类(Persistent Collections)
映射集合(Mapping a Collection)
值集合和多对多关联(Collections of Values and Many-To-Many Associations)
一对多关联(One-To-Many Associations)
延迟初始化(延迟加载)(Lazy Initialization)
集合排序(Sorted Collections)
使用 <idbag>
双向关联(Bidirectional Associations)
三重关联(Ternary Associations)
异类关联(Heterogeneous Associations)
集合例子
持久化集合类(Persistent Collections)
这部分不包含大量的.NET代码例子。我们假定你已经了解如何使用.NET自身的集合类框架(.NET's collections framework)和Set集合的概念。 其实如果是这样, 这里就真的没有什么东西需要学习了... 用一句话来做个总结,你就用你已经掌握的知识来使用它们吧。
NHibernate可以持久化以下集合的实例, 包括System.Collections.IDictionary, System.Collections.IList, Iesi.Collections.ISet和任何持久实体或值的数组。类型为System.Collections.ILst的属性还可以使用"bag"语义来持久。
警告:用于持久化的集合,除了集合接口外,不能保留任何实现这些接口的类所附加的语义(例如:Iesi.Collections.ListSet带来的迭代顺序iteration order)。所有的持久化集合,实际上都各自按照System.Collections.Hashtable, System.Collections.ArrayList, Iesi.Collections.HashedSet的语义直接工作。更深入地说,对于一个包含集合的属性来说,必须把.NET类型定义为接口(也就是IDictionary, IList或者ISet)。存在这个限制的原因是,在你不知道的时候,NHibernate暗中把你的IDictionary, IList 和 ISet 的实例替换成了它自己的关于这些集合的实现。(所以在你的程序中,谨慎使用==操作符。)
Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
...
Iesi.Collections.ISet kittens = new Iesi.Collections.HashedSet();
kittens.Add( kitten );
cat.Kittens = kittens;
session.Save( cat );
kittens = cat.Kittens; // Okay, kittens collection is an ISet
(Iesi.Collections.HashedSet)cat.Kittens; //Error! - a NHibernate.Collections.Set not Iesi.Collections.HashedSet
集合遵从对值类型的通常规则:不能共享引用, 与其包含的实体共存亡。由于存在底层的关联模型,集合不支持空值语义;并且NHibernate不会区分一个null的集合引用和一个不存在元素的空集合。
集合类在被一个持久化对象引用的时候,会自动持久化,当不再被引用时将会自动删除。如果一个集合被从一个持久化对象传递到另一个,它的元素可能会从一个表转移到另一个表。你应该不需要对此特别关心。就如同你使用普通的 .NET集合类一样使用NHibernate的集合类,但是你需要确信使用前你理解了双向关联的语义(后面会讨论)。
集合实例在数据库中根据指向对应实体的外键而得到区别。这个外键被称为集合的关键字。在NHibernate配置文件中使用 <key> 元素来映射这个集合的关键字。
集合可以包含几乎所有的其他NHibernate类型, 包括所有的基本类型, 自定义类型,实体类型和组件。有一条重要的定义:在集合中的对象可以通过“传值”语义(完全依赖于集合自身)操作,也可以是一个指向其他实体的引用,拥有自己的生命周期。集合不能包含其他集合。这些被包含的元素的类型被称为集合元素类型。集合的元素在Hibernate中被映射为<element>, <composite-element>, <one-to-many>, <many-to-many> 或者<many-to-any>。前两种用传值语义操作元素,另外三种则映射实体关联。
除了ISet和Bag之外的所有集合类型都有一个索引(index)字段,这个字段映射到一个数组或者IList的索引或者IDictionary的key。IDictionary的索引的类型可以是任何基本类型, 实体类型或者甚至是一个组合类型(但不能是一个集合类型)。数组和IList的索引肯定是整型(Int32)。在NHibernate配置文件中使用 <index>,<index-many-to-many> ,<composite-index> 或者<index-many-to-any>等元素来映射索引。
集合类可以产生相当多种类的映射,涵盖了很多通常的关系模型。我们建议你练习使用schema生成工具, 以便对如何把不同的映射定义转换为数据库表有一个感性认识。
映射集合(Mapping a Collection)
值集合和多对多关联(Collections of Values and Many-To-Many Associations)
一对多关联(One-To-Many Associations)
延迟初始化(延迟加载)(Lazy Initialization)
集合排序(Sorted Collections)
使用 <idbag>
双向关联(Bidirectional Associations)
双向关联允许通过关联的任一端访问另外一端。在NHibernate中, 支持两种类型的双向关联:
一对多(one-to-many):<set>或者<bag>值在一端, 单独值(非集合)在另外一端
多对多(many-to-many):两端都是<set>或<bag>值
请注意NHibernate不支持带有索引的集合(IList,IDictionary或者数组)作为"多"的那一端的双向one-to-many关联,你必须使用集合或者bag映射.
要建立一个双向的多对多关联,只需要映射两个many-to-many关联到同一个数据库表中,并再定义其中的一端为inverse(使用哪一端要根据你的选择)。这里有一个从一个类关联到他自身的many-to-many的双向关联的例子(每一个category都可以有很多items,每一个items可以属于很多categories):
<class name name="NHibernate.Auction.Category, NHibernate.Auction">
<id name="Id" column="ID"/>
...
<bag name="Items" table="CATEGORY_ITEM" lazy="true">
<key column="CATEGORY_ID" />
<many-to-many class="NHibernate.Auction.Category, NHibernate.Auction" column="ITEM_ID" />
</bag>
</class>
<class name="NHibernate.Auction.Item, NHibernate.Auction">
<id name="Id" column="ID" />
<!-- inverse end -->
<bag name="Categories" table="CATEGORY_ITEM" inverse="true" lazy="true">
<key column="ITEM_ID" />
<many-to-many class="NHibernate.Auction.Category, NHibernate.Auction" column="CATEGORY_ID" />
</bag>
</class>
如果只对关联的反向端进行了改变,这个改变不会被持久化。 这表示NHibernate为每个双向关联在内存中存在两次表现,一个从A连接到B,另一个从B连接到A。如果你回想一下.NET对象模型,我们是如何在.NET中创建多对多关系的,这可以让你更容易理解:
category.Items.Add( item ); // The category now "knows" about the relationship
item.Categories.Add( category ); // The item now "knows" about the relationship
session.Update( item ); // No effect, nothing will be saved!
session.Update( category ); // The relationship will be saved
非反向端用于把内存中的表示保存到数据库中。如果两端都进行了改编,我们会进行多余的INSERT/UPDATE,甚至可能得到外键冲突!这一点对双向的一对多关联也是一样的。
要建立一个一对多的双向关联,你可以通过把一个一对多关联,作为一个多对一关联映射到到同一张表的字段上,并且在"多"的那一端定义inverse="true"。
<class name="Eg.Parent, Eg>
<id name="Id" column="id" />
...
<set name="Children" inverse="true" lazy="true">
<key column="parent_id" />
<one-to-many class="Eg.Child, Eg" />
</set>
</class>
<class name="Eg.Child, Eg">
<id name="Id" column="id" />
....
<many-to-one name="Parent" class="Eg.Parent, Eg" column="parent_id" />
</class>
在“一”这一端定义inverse="true"不会影响级联操作,二者是不同的概念!
三重关联(Ternary Associations)
异类关联(Heterogeneous Associations)
集合例子
第6章 关联映射
目录
简介
单向关联
多对一(many to one)
一对一(one to one)
一对多(one to many)
使用表连接的单向关联
一对多(one to many)
多对一(many to one)
一对一(one to one)
多对多(many to many)
双向关联
一对多(one to many) / 多对一(many to one)
一对一(one to one)
使用表连接的双向关联
一对多(one to many) / 多对一(many to one)
一对一(one to one)
多对多(many to many)
简介
单向关联是最常用的也是最难正确使用的。在本章中会逐个经历规范的案例, 从单向映射开始,然后涉及双向的案例。我们会在所有的例子中使用Person和 Address。例子中没有包括命名空间和程序集,我们把关注点放在重要的方面。
我们通过是否使用表连接和多样性(单向或双向)分类关联。
在传统的数据模型中允许为空的外键是不实用的,所以我们的例子中没有使用允许为空的外键。在NHibernate中这不是必须的,如果你删除空值的约束, 映射会照常工作。
单向关联
多对一(many to one)
一对一(one to one)
一对多(one to many)
使用表连接的单向关联
一对多(one to many)
多对一(many to one)
一对一(one to one)
多对多(many to many)
双向关联
一对多(one to many) / 多对一(many to one)
双向的一对多(one-to-many)关联是普通的关联类型。(这是标准的parent/child关系。)
<class name="Person">
<id name="Id" column="personId">
<generator class="native" />
</id>
<many-to-one name="Address"
column="addressId"
not-null="true"
/>
</class>
<class name="Address">
<id name="Id" column="addressId">
<generator class="native" />
</id>
<set name="People" inverse="true">
<key column="addressId" />
<one-to-many class="Person" />
</set>
</class>
create table Person
(
personId bigint not null primary key,
addressId bigint not null
)
create table Address
(
addressId bigint not null primary key
)
一对一(one to one)
使用表连接的双向关联
一对多(one to many) / 多对一(many to one)
一对一(one to one)
多对多(many to many)
第7章示例: Parent/Child
目录
关于collections
双向的一对多关系(Bidirectional one-to-many)
级联生命周期(Cascading lifecycle)
级联更新(Using cascading update())
结论
刚刚接触NHibernate的人大多是从父子关系(parent / child type relationship)的建模入手的。父子关系的建模有两种方法。比较简便、直观的方法就是在实体类Parent和Child之间建立 <one-to-many>的关联关系,从Parent指向Child,对新手来说尤其如此。但还有另一种方法,就是将Child声明为一个<composite-element> (组合元素)。可以看出在NHibernate中使用一对多关联比composite element更接近于通常parent / child关系的语义。下面我们会阐述如何使用双向可级联的一对多关联(bidirectional one to many association with cascades)去建立有效、优美的parent / child关系。这一点也不难!
关于collections
在NHibernate下,实体类将collection作为自己的一个逻辑单元,而不是被容纳的多个实体。这非常重要!它主要体现为以下几点:
l 当删除或增加collection中对象的时候,拥有这个collection的实体对象的版本值会递增。
l 如果一个从collection中移除的对象是一个值类型(value type)的实例,比如composite element,那么这个对象的持久化状态将会终止,其在数据库中对应的记录会被删除。同样的,向collection增加一个value type的实例将会使之立即被持久化。
l 另一方面,如果从一对多或多对多关联的collection中移除一个实体( 一对多one-to-many 或者 多对多many-to-many关联),在缺省情况下这个对象并不会被删除。这个行为是完全合乎逻辑的--改变一个实体的内部状态不应该使与它关联的实体消失掉!同样的,向collection增加一个实体不会使之被持久化。
实际上,向Collection增加一个实体的缺省动作只是在两个实体之间创建一个连接而已,同样移除的时候也只是删除连接。这种处理对于所有的情况都是合适的。不适合所有情况的其实是父子关系本身,因为子对象是否存在依赖于父对象的生存周期。
双向的一对多关系(Bidirectional one-to-many)
让我们从一个简单的例子开始,假设要实现一个从类Parent到类Child的一对多关系。
<set name="Children">
<key column="parent_id" />
<one-to-many class="Child" />
</set>
如果我们运行下面的代码
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = new Child();
p.Children.Add( c );
session.Save( c );
session.Flush();
NHibernate就会产生下面的两条SQL语句:
l 一条INSERT语句,用于创建对象c对应的数据库记录
l 一条UPDATE语句,用于创建从对象p到对象c的连接
这样做不仅效率低,而且违反了列parent_id非空的限制。
底层的原因是,对象p到对象c的连接(外键parent_id)没有被当作是Child对象状态的一部分,也没有在INSERT的时候被创建。解决的办法是,在Child一端设置映射。
<many-to-one name="Parent" column="parent_id" not-null="true"
(我们还需要为类Child添加Parent属性)
现在实体Child在管理连接的状态,为了使collection不更新连接,我们使用inverse属性。
<set name="Children" inverse="true">
<key column="parent_id" />
<one-to-many class="Child" />
</set>
下面的代码是用来添加一个新的Child
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = new Child();
c.Parent = p;
p.Children.Add( c );
session.Save( c );
session.Flush();
现在,只会有一条INSERT语句被执行!
为了让事情变得井井有条,可以为Parent加一个AddChild()方法
public void AddChild( Child c )
{
this.Children.Add( c );
c.Parent = this;
}
AddChild把代码简化了
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = new Child();
p.AddChild( c ); //
session.Save( c );
session.Flush();
级联生命周期(Cascading lifecycle)
对每个对象调用Save() ()方法很麻烦,我们可以用级联来解决这个问题。
<set name="Children" inverse="true" cascade="all">
<key column="parent_id" />
<one-to-many class="Child" />
</set>
配置级联以后,代码就简化了:
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = new Child();
p.AddChild( c );
session.Flush();
注意
级联十分依赖unsaved-value属性(attribute)。请确保<id>属性(property)的默认值和unsaved-value一样。
类似的,当保存或删除Parent时我们不需要遍历children。下面的代码从数据库中删除了p和它所有的children。
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
session.Delete( p );
session.Flush();
然而,这段代码
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = null;
foreach( Child child in p.Children )
{
c = child; // only care about first Child
break;
}
p.Children.Remove( c );
c.Parent = null;
session.Flush();
不会从数据库删除c;它只会删除与p之间的连接(并且会导致违反NOT NULL约束,在这个例子中)。你需要明确调用Child的Delete() 方法。
Parent p = session.Load( typeof( Parent ), pid ) as Parent;
Child c = null;
foreach( Child child in p.Children )
{
c = child; // only care about first Child
break;
}
p.Children.Remove( c );
c.Parent = null;
session.Delete( c );
session.Flush();
在我们的例子中,如果我们规定没有父对象的话,子对象就不应该存在,如果将子对象从collection中移除,实际上我们是想删除它。要实现这种要求,就必须使用cascade="all-delete-orphan"。
<set name="Children" inverse="true" cascade="all-delete-orphan">
<key column="parent_id" />
<one-to-many class="Child" />
</set>
注意:即使在collection一方的映射中指定iinverse="true",在遍历collection的时候级联操作仍然会执行。如果你想要通过级联进行子对象的插入、删除、更新操作,就必须把它加到collection中,只调用Child.Parent 的setter是不够的。
级联更新(Using cascading update())
假设我们从ISession中装入了一个Parent对象,用户界面对其进行了修改,然后我们希望在一个新的ISession里面调用 Update()来更新它。对象Parent包含了子对象的集合,由于打开了级联更新,NHibernate需要知道哪些子对象是新的,哪些是数据库中已经存在的。我们假设Parent和Child对象的标识属性的类型为System.Int32。NHibernate会使用标识属性的值来判断哪些子对象是新的。(你也可以使用version 或 timestamp 属性)
unsaved-value属性是用来表示新实例的标识属性值的,缺省为"null",对于.net的值类型(ValueTypes)这不是一个好的默认值,所以你需要提供unsaved-value。
如果我们使用原始类型作为标识类型的话,我们在配置Child类映射的时候就必须写:
<id name="Id" type="Int64" unsaved-value="0">
对于Child 映射. (也有unsaved-value属性(attribute)提供给版本(version)和时间戳(timestamp)属性(property)映射)。下面的代码会更新parent和child对象,并且插入newChild对象。
//parent and child were both loaded in a 上一页ious session
parent.AddChild( child );
Child newChild = new Child();
parent.AddChild( newChild );
session.Update( parent );
session.Flush();
好的,对于自动生成标识的情况这样做很方便,但是自分配的标识和复合标识怎么办呢?这是有点麻烦,因为unsaved-values无法区分新对象(标识是用户指定的)和前一个ISession装入的对象。在这种情况下,你可能需要给NHibernate一些提示,在调用update(parent)之前:
l 在这个类的 <version> or <timestamp>属性映射上定义unsaved-value="null"或者unsaved-value="negative"。
l 在对父对象执行Update( parent )之前,设定unsaved-value="none" 并且显式的调用Save()在数据库创建新子对象
l 在对父对象执行Update( parent )之前,设定unsaved-value="any"并且显式的调用 Update()更新已经装入的子对象
none是自分配标识和复合标识的unsaved-value的缺省值。
结论
这个问题往往让新手感到迷惑,它确实不太容易消化。不过,经过一些实践以后,你会感觉越来越顺手。父子对象模式已经被广泛的应用在NHibernate应用程序中。
在第一段中我们曾经提到另一个方案。复合元素的语义与父子关系是等同的,但是我们并没有详细讨论。很不幸复合元素还有两个重大限制:复合元素不能拥有collections,并且,除了用于惟一的父对象外,它们不能再作为其它任何实体的子对象。(但是,通过使用 <idbag>映射,它们可能拥有代理主键。)
第8章 NHibernate缓存(NHibernate.Caches)
什么是 NHibernate.Caches?
NHibernate.Caches 是 NHibernate 的附加软件,它是Kevin Williams (aka k-dub)贡献的.缓存是一个保存实体的地点(在首次加载时);一旦进入缓存,能够取得它们,而无需(再次)查询的后台的存储(数据库)。这意味着它们能更快的加载(或重新加载)。
NHibernate session有一个内部的(一级)缓存,存放着它的实体。这些缓存没有共享,因此session被销毁时它的缓存也被销毁了。NHibernate提供了二级缓存系统;他在SessionFactory级别工作。因此它被同一个SessionFactory产生的session共享。
使用每个请求(request)一个session模式,很多个Session可以并发的访问同一个实体,而不用每次都访问数据库,因此性能获得了提升。
贡献者使得在NHibernate中使用不同的缓存提供者成为可能:
l NHibernate.Caches.Prevalence使得使用底层的 Bamboo.Prevalence实现作为缓存提供者成为可能。打开文件Bamboo.Prevalence.license.txt可以看到它的许可信息,你也可以访问它的站点。
l NHibernate.Caches.SysCache使得使用底层的System.Web.Caching.Cache实现作为缓存提供者成为可能。这意味着你可以依赖ASP.NET的缓存特性来理解它是怎么工作的。要得到更多的信息,可以阅读Caching Application Data(在NSDN上)。
如何使用?
这里是在NHibernate中启用二级缓存的步骤:
l 选择需要使用的缓存提供者并且拷贝它的程序集到你的程序集路径(NHibernate.Caches.Prevalence.dll 或者NHibernate.Caches.SysCache.dll).
l 为了表明使用哪种缓存提供者,在NHibernate配置文件中(可以在YourAssembly.exe.config或者web.config或者.cfg.xml 文件)添加下面的内容:
l <add key="hibernate.cache.provider_class" value="XXX" />(1)
l <add key="relativeExpiration" value="120" />(2)
href="#hibernate.cache.provider_class-co" (1) |
"XXX" 可以是 "NHibernate.Caches.Prevalence.PrevalenceCacheProvider, NHibernate.Caches.Prevalence" 或者"NHibernate.Caches.SysCache.SysCacheProvider, NHibernate.Caches.SysCache"。 |
href="#nhcaches-relativeExpiration-co" (2) |
relativeExpiration的值是你希望缓存每个实体的秒数(这里是两分钟) |
l 添加<cache usage="read-write|nonstrict-read-write|read-only"/>(在<class>后面)到你需要缓存的实体映射中.它也为集合(bag, list, map, set, ...)提供支持.
小心:缓存不会知道另一个进程存储的实体的变化(尽管配置了缓存数据的过期时间).当缓存被建立在SessionFactory级别,他们会和SessionFactory实例一起被销毁; 所以必须在你需要缓存的时候,保持SessionFactory存在.
第9章 使用AttributesNHibernate.Mapping.Attributes
目录
如何使用?
提示
已知的问题和TODOs
开发者须知
什么是 NHibernate.Mapping.Attributes?
NHibernate.Mapping.Attributes 是 NHibernate 的附加软件,它是Pierre Henri Kuat (aka KPixel)贡献的; 以前的实现者是 John Morris.NHibernate需要映射信息来绑定你的域对象到数据库。通常他们被写在(并且被保存在)在分散的hbm.xml文件里。
使用NHibernate.Mapping.Attributes,你可以使用.NET属性(attributes)来修饰你的实体和被用于产生.hbm.xml映射(文件或者流)的属性(attributes)。因此,你将不再会为这些令人厌恶的文件而烦恼。
这个库里面的内容包括。
l NHibernate.Mapping.Attributes: 你需要的唯一工程(作为最终用户)
l Test:一个使用属性(attributes)和HbmSerializer的简单用例,是NUnit的TestFixture
l Generator: 用来产生属性(attributes) 和HbmWriter的程序.
l Refly: 感谢Jonathan de Halleux 提供这个库,它使得产生代码变得如此简单.
重要提示
这个库是使用文件/src/NHibernate.Mapping.Attributes/nhibernate-mapping-2.0.xsd(它嵌入在程序集中能够检查产生的XML流的合法性)产生的.这个文件可能在NHibernate每次发布新版本时发生变化,所以你应该在不同的版本中使用它时,重新生成它(打开Generator工程,编译并且运行Generator项目).但是,在0.8之前的版本中它并没有通过测试.
如何使用?
最终用户类 是NHibernate.Mapping.Attributes.HbmSerializer.这个类序列化你的域模型到映射流.你可以逐个序列化程序集中的类.NHibernate.Mapping.Attributes.Test可以作为参考.
第一步用属性(attributes)修饰你的实体;你可以用 [Class], [Subclass], [JoinedSubclass]或者[Component].然后,修饰成员(字段/属性properties);它们能够代替很多映射中需要使用的属性(attributes ),例如:
[NHibernate.Mapping.Attributes.Class]
public class Example
{
[NHibernate.Mapping.Attributes.Property]
public string Name;
}
完成这个步骤后,使用NHibernate.Mapping.Attributes.HbmSerializer:(这里我们使用了Default ,它是一个实例,在你不必须/不想自己创建它时使用).
System.IO.MemoryStream stream = new System.IO.MemoryStream(); // where the xml will be written
NHibernate.Mapping.Attributes.HbmSerializer.Default.Validate = true; // Enable validation (可选)
// Here, we serialize all decorated classes (but you can also do it class by class)
NHibernate.Mapping.Attributes.HbmSerializer.Default.Serialize(
stream, System.Reflection.Assembly.GetExecutingAssembly() );
stream.Position = 0; // Rewind
NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();
cfg.Configure();
cfg.AddInputStream(stream); // Use the stream here
stream.Close();
// Now you can use this configuration to build your SessionFactory...
注意:正如你所见:NHibernate.Mapping.Attributes是没有(真正的)侵入性的.在你的对象上设置属性(attributes ),不会强迫你在NHibernate 使用它们,并且不会破坏你的架构体系中的任何约束.属性(Attributes)仅仅是纯粹的信息.
提示
l 使用HbmSerializer.Validate来启用/禁用产生的xml流的合法性检查(依靠NHibernate mapping schema);对于快速查找问题这是很有用的(它们被StringBuilder写入HbmSerializer.Error).如果错误是这个库预期的,库会看它是否是已知的问题并且报告它;解决这些问题能帮助你完成你的解决方案. :)
l 你的类,字段和属性properties(成员)可以是私有的;请确认你有使用反射访问私有成员的权限(ReflectionPermissionFlag.MemberAccess).
l 映射类的成员也会在基类中查找(直到找到映射的基类).因此,你可以在基类(没有做映射)中修饰成员,然后在它的(做过映射的)子类中使用它.
l 对于一个有类型(System.Type)的Name,使用Name="xxx"(作为string)设置类型或者设置NameType=typeof(xxx);(给 "Name"添加"类型")
l 默认情况下,.NET属性(attributes)没有维持属性(attributes)的顺序;因此,你必须自己设置顺序,在你管理顺序的时候(使用每个属性的第一个参数).强烈推荐设置它,当你的一个成员上有超过一个属性(attribute )时.
l 只要不产生含糊,你可以在成员上定义很多不相干的属性(attributes).一个好的例子是在标识符成员上的类描述(class-related)属性(attributes )(类似鉴别器<discriminator>).但是不要忘记管理顺序(<discriminator> 必须在<id>的后面).顺序来自NHibernate mapping schema中元素(elements)的顺序.在我个人看来,我更喜欢在属性上使用负数(如果它们是在前面的!).
l 你可以在类上面加 [HibernateMapping] 来指定<hibernate-mapping> 属性(attributes)(当类被序列化成流时使用).你也可以使用HbmSerializer.Hbm*属性(properties)(当被[HibernateMapping]修饰的类型或程序集被序列化时被使用).
l 不使用一个字符串作为鉴别器值(DiscriminatorValue)(在[Class]和[Subclass]),你可以在任何你需要的对象上这样使用.例子: [Subclass(DiscriminatorValueEnumFormat="d", DiscriminatorValueObject=DiscEnum.Val1)]
在这里,对象是一个枚举,你可以设置你需要的格式(默认的值是"g").注意你必须把它放在前面!对于其他的类型,只是简单的使用了对象的ToString()方法.
l 如果你使用Nullables.NullableXXX类型的的成员(在库Nullables中),系统会自动映射到Nullables.NHibernate.NullableXXXType;不用在[Property]中设置Type="..."(让它为空).感谢Michael Third的这个主意. :)
l NHibernate.Mapping.Attributes产生的每个流都有一个产生日期的注释;你可以通过方法WriteDateComment启用/禁用它.
l 如果你忘记提供一个必须的xml属性(attribute),系统会抛出一个异常,在创建映射时.
l 映射[Component] 时,被推荐的并且最简单的方式是使用[ComponentProperty].
首先,放置[Component]在组件类并且映射它的字段/属性.注意不要在[Component]设置名字.然后,在你的类的每个成员,添加[ComponentProperty].但是你不能改变每个成员的存取(Access),更新(Update)或插入(Insert).
在NHibernate.Mapping.Attributes.Test里有一个例子(注意CompAddress类和他在其他类中的使用).
注意最后一件事情:ComponentPropertyAttribute是从DynamicComponentAttribute继承来的,容易把它紧接着写在 <component>元素的后面,在XML流中.
l 另一个映射[Component]的方式是,它用这种方法让库工作:如果一个类包含了一个组件映射,那么这个组件将会被类包含.NHibernate.Mapping.Attributes.Test包含JoinedBaz和Stuff使用地址(Address)组件的例子. 很简单的,添加了以后
[Component(Name = "MyComp")] private class SubComp : Comp {}
在所有类中,一个优势是能够改变每个成员的存取(Access),更新(Update)或插入(Insert).但是,你必须添加组件子类到每个类中(并且它不能被继承).
l 关于自定义。HbmSerializer使用HbmWriter序列化各种属性(attributes)。他的方法是虚的;因此你可以创建一个子类,重写任何方法(来改变它的默认行为)。
使用属性(property)HbmSerializer.HbmWriter来改变写的实现者。(你可以设置一个HbmWriter的子类)。
使用了部分提示的例子:(0,1和2按顺序排列)
[NHibernate.Mapping.Attributes.Id(0, TypeType=typeof(int))] // Don't put it after [ManyToOne] !!!
[NHibernate.Mapping.Attributes.Generator(1, Class="uuid.hex")]
[NHibernate.Mapping.Attributes.ManyToOne(2, ClassType=typeof(Foo), OuterJoin=OuterJoinStrategy.True)]
private Foo Entity;
产生的:
<id type="Int32">
<generator class="uuid.hex" />
</id>
<many-to-one name="Entity" class="Namespaces.Foo, SampleAssembly" outer-join="true" />
已知的问题和TODOs
首先,阅读源代码里面的TODOs
Position属性(property)被加在所有属性(attributes)上,用来给他们排序。但是仍然有问题:
当一个父元素"p"有一个子元素"x",它的另一个子元素"c"有子元素"x"。:D 如图
<p>
<c>
<x />
</c>
<x />
</p>
在这个例子中,如果这样写:
[Attributes.P(0)]
[Attributes.C(1)]
[Attributes.X(2)]
[Attributes.X(3)]
public MyType MyProperty;
X(3)将会属于C(1)!(和X(2)一样)
下面是<dynamic-component>和<nested-composite-element>的情况。
另一个坏消息是,现在,后来加入的XML元素不能被包含.例如:没有办法在<dynamic-component>放置集合.原因是nhibernate-mapping-2.0.xsd文件告诉程序元素怎么被创建,按照什么顺序被创建,并且NHibernate.Mapping.Attributes按这个顺序使用它们.
总之,解决方案应该添加整型的ParentNode属性(property)给BaseAttribute,这样你能够创建一个真实的情况...
实际上,没有其他的知识点了而且也没有计划好的修改.这个库将会成为稳定的完整版本;但是你发现了问题或者有有效的改进想法,请联系我们!
另一个消息,希望有比NHibernate.Mapping.Attributes.Test更好的TestFixture.:D
开发者须知
schema (nhibernate-mapping-2.0.xsd)的任何改变意味着:
l 检查是否要在Generator中做任何改变(象updating KnowEnums / AllowMultipleValue / IsRoot / IsSystemType / IsSystemEnum / CanContainItself)
l 更新/src/NHibernate.Mapping.Attributes/nhibernate-mapping-2.0.xsd (复制/粘贴),并且再次运行Generator(即使你没有修改)
l 运行测试项目,确定没有已知的异常抛出.应该在可以确保能够把握改变带来的破坏时,修改/添加这个项目中一个类/属性(property)(=>更新hbm.xml文件和/或NHibernate.Mapping.Attributes-1.1.csproj项目的引用)
这个实现基于NHibernate mapping schema;有可能很多"标准schema特性"没有被支持...
这个版本的NHibernate.Mapping.Attributes需要使用NHibernate库的版本的schema来产生.
这个项目的设计,性能是一个(十分)小的目标,实现和维护则要重要许多.
第10章 NHibernate.Tool.hbm2net
什么是 NHibernate.Tool.hbm2net?
NHibernate.Tool.hbm2net 是 NHibernate 的附加软件.它使得从hbm.xml映射文件产生源代码成为可能。
在 NHibernate.Tasks目录,有一个叫做Hbm2NetTask的工具,你可以用它自动编译程序(使用NAnt)。
第11章 Nullables
什么是 Nullables?
Nullables 是 NHibernate 的附加软件,它是Donald L Mull Jr. (aka luggage)贡献的.大部分数据库系统允许基本类型(象int或bool)为null。这意味着一个boolean列可能有0,1或者是null值,null和0有不同的含义。但是在.NET 1.x这是不能实现的;一个bool不是true就是false。
Nullables使得在NHibernate中使用nullable的基本类型成为可能。注意,.NET 2.0已经有了这个特性。
如何使用?
这里是一个简单的例子,它使用了Nullables.NullableDateTime来(可选择的)保存一个人(Person)的生日。
public class Person
{
int _id;
string _name;
Nullables.NullableDateTime _dateOfBirth;
public Person()
{
}
public int Id
{
get { return this._id; }
}
public string Name
{
get { return this._name; }
set { this._name = value; }
}
public Nullables.NullableDateTime DateOfBirth
{
get { return this._dateOfBirth; }
set { this._dateOfBirth = value; }
}
}
如你所见,DateOfBirth是Nullables.NullableDateTime类型(而不是System.DateTime)。这里是映射
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="Example.Person, Example" table="Person">
<id name="Id" access="field.camelcase-underscore" unsaved-value="0">
<generator class="native" />
</id>
<property name="Name" type="String" length="200" />
<property name="DateOfBirth" type="Nullables.NHibernate.NullableDateTimeType, Nullables.NHibernate" />
</class>
</hibernate-mapping>
重点
在这个映射中,DateOfBirth的类型必须是Nullables.NHibernate.NullableDateTimeType。注意NHibernate.Mapping.Attributes会自动处理它。
Nullables.NHibernate.NullableXXXType是用来转换Nullables 类型到数据库的包装类。
这里是这个例子的部分代码:
Person per = new Person();
textBox1.Text = per.DateOfBirth.Value.ToString() // will throw an exception when there is no value.
textBox1.Text = per.DateOfBirth.ToString() // will work. it will return an empty string if there is no value.
textBox1.Text = (per.DateOfBirth.HasValue ? per.DateOfBirth.Value.ToShortDateString() : "Unknown") // friendly message
per.DateOfBirth = new System.DateTime(1979, 11, 8); // implicit cast from the "plain" System.DateTime.
per.DateOfBirth = new NullableDateTime(new System.DateTime(1979, 11, 8)); // the long way.
per.DateOfBirth = null; // this works.
per.DateOfBirth = NullableDateTime.Default; // this is more correct.