NHibernate学习步骤

1、了解什么事NHibernate及他的优点

 

NHibernate 是一个面向.NET 环境的对象/关系数据库映射工具。对象关系映射(O/R Mapping,Object Relational Mapping)表示一种技术,用来把对象模型表示的对象映射到基于SQL 的关系模型数据结构中去。

NHibernate不仅仅管理.NET 类到数据库表的映射(包括.NET 数据类型到SQL 数据类型的映射),还提供数据查询和获取数据的方法,大幅度减少我们开发时人工使用SQL和ADO.NET处理数据的时间。NHibernate的目标是对于开发者通常的数据持久化相关的编程任务,解放其中的95%。并请记住NHibernate作为数据库访问层,是与你的程序紧密集成的。

 

2、 NHibernate的架构

你知道NHibernate到底什么样子?下面我摘取官方文档中的三幅不同的结构图稍做说明。

第一幅图:NHibernate体系结构非常抽象的概览

概览

这幅图展示了NHibernate在数据库和应用程序之间提供了一个持久层。

第一幅图好像非常简单?其实NHibernate是比较复杂的。我们了解两种极端情况,轻量级和重量级架构。再来第二幅图:轻量级体系,应用程序自己提供ADO.NET连接,并且自行管理事务。

轻量级体系

最后一张图:重量级体系:所有的底层ADO.NET API都被抽象了。

重量级体系

 

3、认识O/R Mapping

 

对象和关系数据库之间的映射是用一个XML文档(XML document)来定义的。这个映射文档被设计为易读的,并且可以手工修改。映射语言是以.NET为中心的,意味着映射是按照持久化类的定义来创建的,而非表的定义。

 

<? 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 >

 3.1认识映射中属性的含义

 

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)
> 
(1)

schema (可选): 数据库schema名称.

(2)

default-cascade (可选 - 默认为 none): 默认的级联风格.

(3)

auto-import (可选 - 默认为 true): 指定是否我们可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。

(4)

default-access (可选 - 默认为 property): NHibernate访问属性值时的策略。

(5)

assembly (可选): 指定一个程序集,如果在映射文档中没有指定程序集,就使用这个程序集。

(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)
/>
(1)

name: 持久化类(或者接口)的全限定名。

(2)

table: 对应的数据库表名。

(3)

discriminator-value (可选 - 默认和类名一样): 一个用于区分不同的子类的值,在多态行为时使用。

(4)

mutable (可选, 默认为 true): 表明该类的实例可变(不可变)。

(5)

schema (可选): 覆盖在根<hibernate-mapping> 元素中指定的schema名字。

(6)

proxy (可选): 指定一个接口,在延迟装载时作为代理使用。你可以在这里使用该类自己的名字。

(7)

dynamic-update (可选, 默认为 false): 指定用于UPDATE 的SQL将会在运行时动态生成,并且只更新那些改变过的字段。

(8)

dynamic-insert (可选, 默认为 false): 指定用于INSERT的 SQL 将会在运行时动态生成,并且只包含那些非空值字段。

(9)

polymorphism (可选, 默认为 implicit(隐式)): 界定是隐式还是显式的使用查询多态。

(10)

where (可选) 指定一个附加的SQL WHERE 条件,在抓取这个类的对象时会一直增加这个条件。

(11)

persister (可选): 指定一个定制的 IClassPersister.

(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-updatedynamic-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>		
(1)

name (可选): 标识属性的名字。

(2)

type (可选): 标识NHibernate类型的名字。

(3)

column (可选 - 默认为属性名): 主键字段的名字。

(4)

unsaved-value (可选 - 默认为 null): 一个特定的标识属性值,用来标志该实例是刚刚创建的,尚未保存。这可以把这种实例和从以前的session中装载过(可能又做过修改--译者注)但未再次持久化的实例区分开来。

(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_keynext)作为高位值得来源。高/低位算法生成的标识符只在一个特定的数据库中是唯一的。

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)

hiloseqhilo生成器给出了两种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中说明一种更加方便的方法,把联合标识实现为一个独立的类,下面描述的属性只对这种备用方法有效:

(1)

name (可选): 一个组件类型,持有联合标识(参见下一节)。

(2)

class (可选 - 默认为通过反射(reflection)得到的属性类型): 作为联合标识的组件类名(参见下一节)。

(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)
/>
(1)

column (可选 - 默认为 class) 识别器字段的名字

(2)

type (可选 - 默认为 String) 一个NHibernate字段类型的名字

(3)

force (可选 - 默认为 false) "强制"NHibernate指定允许的识别器值,就算取得的所有实例都是根类的。

(4)

insert (可选 - 默认为 true) 当识别器是被映射的组件的标识符的一部分时设置为false

标识器字段的实际值是根据<class><subclass>元素的discriminator-value得来的.

force属性仅仅是在表包含一些未指定应该映射到哪个持久化类的时候才是有用的。这种情况不是经常会遇到。

版本(version)(可选)

元素<version>是可选的,表明表中包含附带版本信息的数据。这在你准备使用长事务(long transactions)的时候特别有用。(见后)

<version
	column="version_column"(1)
	name="propertyName"(2)
	type="typename"(3)
	access="field|property|nosetter|ClassName"(4)
	unsaved-value="null|negative|undefined|value"(5)
/>
(1)

column (可选 - 默认为属性名): 指定持有版本号的字段名。

(2)

name: 持久化类的属性名。

(3)

type (可选 - 默认是 Int32): 版本号的类型。

(4)

access (可选 - 默认是 property): NHibernate用于访问属性值的策略。

(5)

unsaved-value (可选 - 默认是 undefined): 用于标明某个实例时刚刚被实例化的(尚未保存)版本属性值,依靠这个值就可以把这种情况和已经在先前的session中保存或装载的实例区分开来。(undefined指明使用标识属性值进行这种判断。)

版本号必须是以下类型: Int64, Int32, Int16, Ticks, Timestamp, 或者 TimeSpan.

时间戳(timestamp)(可选)

可选的<timestamp>元素指明了表中包含时间戳数据。这用来作为版本的替代。时间戳本质上是一种对乐观锁定的一种不是特别安全的实现。当然,有时候应用程序可能在其他方面使用时间戳。

<timestamp
	column="timestamp_column"(1)
	name="propertyName"(2)
	access="field|property|nosetter|ClassName"(3)
	unsaved-value="null|negative|undefined|value"(4)
/>
(1)

column (可选 - 默认为属性名): 持有时间戳的字段名。

(2)

name: 在持久化类中的属性名,其类型是DateTime.

(3)

access (可选 - 默认是 property): NHibernate用于访问属性值的策略。

(4)

unsaved-value (可选 - 默认是 undefined): 用于标明某个实例时刚刚被实例化的(尚未保存)版本属性值,依靠这个值就可以把这种情况和已经在先前的session中保存或装载的实例区分开来。(undefined指明使用标识属性值进行这种判断。)

注意, <timestamp><version type="timestamp">是等价的。

property

<property> 元素为类声明了一个持久化的属性。

<property
	name="propertyName"(1)
	column="column_name"(2)
	type="typename"(3)
	update="true|false"(4)
	insert="true|false"(5)
	formula="arbitrary SQL expression"(6)
	access="field|property|nosetter|ClassName"(7)
/>
(1)

name:属性的名字。

(2)

column (可选 - 默认为属性名字): 对应的数据库字段名。

(3)

type (可选): 一个NHibernate类型的名字。

(4)

update (可选 - 默认为 true) : 表明在用于UPDATE 的SQL语句中是否包含这个字段。

(5)

insert (可选 - 默认为 true) : 表明在用于INSERT的SQL语句中是否包含这个字段。这二者如果都设置为false则表明这是一个“外源性(derived)”的属性,它的值来源于映射到同一个(或多个)字段的某些其它属性,或者通过一个trigger(触发器),或者其它程序。

(6)

formula (可选): 一个SQL表达式,定义了这个计算(computed) 属性的值。计算属性没有和它对应的数据库字段。

(7)

access (可选 - 默认为 property): NHibernate用来访问属性值的策略。

typename可以是如下几种:

  1. NHibernate 基础类型之一 (比如: Int32, String, Char, DateTime, Timstamp, Single, Byte[], Object, ...).

  2. 基础类型的.NET类型名 (比如: System.Int16, System.Single, System.Char, System.String, System.DateTime, System.Byte[], ...).

  3. 枚举 System.Enum 名 (比如: Eg.Color).

  4. 一个可以序列化的 .NET 类的名字。

  5. 一个自定义类型的类的名字。(比如:Illflow.Type.MyCustomType, Illflow).

如果你没有指定类型,NHibernate会使用反射来得到这个名字的属性,以此来猜测正确的NHibernate类型。NHibernate会对属性读取器(getter访问器)的返回类进行解释,按照规则2,3,4的顺序。然而,这并不足够。 在某些情况下你仍然需要type属性。(比如,为了区别NHibernate.DateTimeNHibernate.Timestamp,或者为了指定一个自定义类型。)

access属性用来让你控制NHibernate如何在运行时访问属性。access的值属性应该用 access-strategy.naming-strategy的格式。.naming-stragey通常不需要设置。

表4.1 访问策略(Access Strategy)

访问策略名描述
property

默认值,NHibernate使用属性的get/set访问。这种访问策略(Access Strategy)不应该使用命名策略(Naming Strategy),因为name属性(attribute)就是属性(Property)的名称。

field

NHibernate将会直接访问字段。NHibernate使用<name>作为字段的名称。当对象属性的get和set里面有额外的动作,而你不想让NHibernate设置或者读取对象时执行额外的动作,可以用这个策略。当你的API的使用者使用HQL时需要属性名而非字段时,就需要命名策略(Naming Strateg)。

nosetter

NHibernate将会在设置值时直接访问字段,获得值时访问属性。当你的API使用者不能直接改变值,因而只为属性只提供了get访问器时,你可以用这个策略。NHibernate使用name属性(attribute)作为属性(Property ),并且需要提供字段名,所以命名策略必须(Naming Strategy)使用。

ClassName

如果NHibernate内置的访问策略(Access Strategie)不能满足你的要求。你可以通过实现NHibernate.Property.IPropertyAccessor接口来自己的访问策略(Access Strategie)。这个值需要用程序集名(Assembly)来限定,这样就能通过 Activator.CreateInstance(string AssemblyQualifiedName)来读取。

 

 

表4.2 命名策略(Naming Strategy)

命名策略描述
camelcase

name属性被转换CamelCase格式来查找字段。<property name="Foo" ... >使用foo字段。

camelcase-underscore

name属性被转换CamelCase格式并添加下划线前缀来查找字段。<property name="Foo" ... >使用_foo字段。

lowercase

name属性被转换小写格式来查找字段。<property name="FooBar" ... > 使用 foobar字段.

lowercase-underscore

name属性被转换小写格式并添加下划线前缀来查找字段。<property name="FooBar" ... >使用_foobar字段.

pascalcase-underscore

name属性添加下划线前缀来查找字段。<property name="Foo" ... >使用_Foo字段。

pascalcase-m-underscore

name属性添加字母m和下划线前缀来查找字段。<property name="Foo" ... > uses the Field m_Foo

 

多对一(many-to-one)

通过many-to-one元素,可以定义一种常见的与另一个持久化类的关联。这种关系模型是多对一关联。(实际上是一个对象引用。)

<many-to-one
        name="propertyName"(1)
        column="column_name"(2)
        class="ClassName"(3)
        cascade="all|none|save-update|delete"(4)
        outer-join="true|false|auto"(5)
        update="true|false"(6)
        insert="true|false"(7)
        property-ref="propertyNameFromAssociatedClass" (8)
        access="field|property|ClassName"(9)
        unique="true|false" (10)
/>
(1)

name: 属性名。

(2)

column (可选):字段名。

(3)

class (可选 - 默认是通过反射得到属性类型): 关联的类的名字。

(4)

cascade (可选): 指明哪些操作会从父对象级联到关联的对象。

(5)

outer-join (可选 - 默认为 auto): 当设置hibernate.use_outer_join的时候,对这个关联允许外连接抓取。

(6)

update, insert (可选 - 默认为 true)指定对应的字段是否在用于UPDATE 和/或 INSERT的SQL语句中包含。如果二者都是false,则这是一个纯粹的“外源性(derived)”关联,它的值是通过映射到同一个(或多个)字段的某些其他属性得到的,或者通过trigger(除法器),或者是其他程序。

(8)

property-ref: (可选)指定关联类的一个属性,这个属性将会和本外键相对应。如果没有指定,会使用对方关联类的主键。

(9)

access (可选 - 默认为property): NHibernate 用来访问属性的策略。

(10)

unique (可选):允许产生外键列唯一约束的数据库定义语言(DDL)。

cascade属性允许下列值:: all, save-update, delete, none. 设置除了none以外的其它值会传播特定的操作到关联的(子)对象中。参见后面的“Lifecycle Objects(自动管理生命周期的对象)”。

outer-join参数允许下列三个不同值:

  • auto (默认) 使用外连接抓取关联(对象),如果被关联的对象没有代理(proxy)

  • true 一直使用外连接来抓取关联

  • false 永远不使用外连接来抓取关联

一个典型的简单many-to-one声明例子:

<many-to-one name="Product" class="Product" column="PRODUCT_ID" />

property-ref属性只应该用来对付老旧的数据库系统,可能出现外键指向对方关联表的是个非主键字段(但是应该是一个惟一关键字)的情况。这是一种十分丑陋的关系模型。比如说,假设Product类有一个惟一的序列号,它并不是主键。(unique属性控制NHibernate通过SchemaExport工具生成DDL的过程。)

<property name="SerialNumber" unique="true" type="String" column="SERIAL_NUMBER" 

那么关于OrderItem 的映射可能是:

<many-to-one name="Product property-ref="SerialNumber" column="PRODUCT_SERIAL_NUMBER" />

当然,我们决不鼓励这种用法。

一对一(one-to-one)

持久化对象之间一对一的关联关系是通过one-to-one元素定义的。

<one-to-one
	name="propertyName"(1)
	class="ClassName"(2)
	cascade="all|none|save-update|delete"(3)
	constrained="true|false"(4)
	outer-join="true|false|auto"(5)
	property-ref="propertyNameFromAssociatedClass" (6)
	access="field|property|ClassName"(7)
/>
(1)

name: 属性的名字

(2)

class (可选 - 默认是通过反射得到的属性类型): 被关联的类的名字.

(3)

cascade (可选) 表明操作是否从父对象级联到被关联的对象。

(4)

contstrainted (可选) 表明该类对应的表对应的数据库表,和被关联的对象所对应的数据库表之间,通过一个外键引用对主键进行约束。这个选项影响Save()Delete()在级联执行时的先后顺序(也在schema export tool中被使用)。

(5)

outer-join (可选 - 默认为 auto):当设置hibernate.use_outer_join的时候,对这个关联允许外连接抓取。

(6)

property-ref: (可选): 指定关联类的一个属性,这个属性将会和本外键相对应。如果没有指定,会使用对方关联类的主键。

(7)

access (可选 - defaults to property): NHibernate 用来访问属性的策略。

有两种不同的一对一关联:

  • 主键关联

  • 惟一外键关联

主键关联不需要额外的表字段;两行是通过这种一对一关系相关联的,那么这两行就共享同样的主关键字值。所以如果你希望两个对象通过主键一对一关联,你必须确认它们被赋予同样的标识值!

比如说,对下面的EmployeePerson进行主键一对一关联:

<one-to-one name="Person" class="Person" />
<one-to-one name="Employee" class="Employee" constrained="true" />

现在我们必须确保PERSON和EMPLOYEE中相关的字段是相等的。我们使用一个特别的称为foreign的NHibernate标识符生成器策略:

<class name="Person" table="PERSON">
	<id name="Id" column="PERSON_ID">
		<generator class="foreign">
			<param name="property">Employee</param>
		</generator>
	</id>
	...
	<one-to-one name="Employee"
		class="Employee"
		constrained="true" />
</class>

一个刚刚保存的Person实例被赋予和该PersonEmployee属性所指向的Employee实例同样的关键字值。

另一种方式是一个外键和一个惟一关键字对应,上面的EmployeePerson的例子,如果使这种关联方式,应该表达成:

<many-to-one name="Person" class="Person" column="PERSON_ID" unique="true" />

如果在Person的映射加入下面几句,这种关联就是双向的:

<one-to-one name="Employee" class="Employee" property-ref="Person" />

组件(component)

<component>元素把子对象的一些元素与父类对应的表的一些字段映射起来。 然后组件可以声明它们自己的属性、组件或者集合。参见后面的“Components”一章。

<component
	name="propertyName"(1)
	class="ClassName"(2)
	insert="true|false"(3)
	update="true|false"(4)
	access="field|property|ClassName"(5)
>
	<parent ... />
	<property ... />
	<many-to-one ... />
	...
<component/>
(1)

name: 属性名

(2)

class (可选 - 默认为通过反射得到的属性类型): 组件(子)类的名字。

(3)

insert: 被映射的字段是否出现在SQL的INSERT语句中?

(4)

update: 被映射的字段是否出现在SQL的UPDATE语句中?

(5)

access (可选 - 默认是 property): NHibernate用来访问属性的策略。

property子标签为子类的一些属性和表字段建立映射。

<component>元素允许加入一个 <parent>子元素,在组件类内部就可以有一个指向其容器的实体的反向引用。

子类(subclass)

最后,多态持久化需要为父类的每个子类都进行声明。对于我们建议的“每一棵类继承树对应一个表”的策略来说,就需要使用<subclass>声明。

<subclass
	name="ClassName"(1)
	discriminator-value="discriminator_value"(2)
	proxy="ProxyInterface"(3)
	dynamic-update="true|false"
	dynamic-insert="true|false"
	extends="BaseMappedClassName"> (4)
	
	<property ... / >
	...
<subclass/>
(1)

name: 子类的全限定名。

(2)

discriminator-value (可选 - 默认为类名): 一个用于区分每个独立的子类的值。

(3)

proxy (可选):指定一个类或者接口,在延迟装载时作为代理使用。

(4)

extends (可选):指定这个子类继承的映射类。只有在使用模块化映射文件(Modular Mapping Files) 时才需要被用到。

连接的子类(joined-subclass)

另外一种情况,如果子类是持久化到一个属于它自己的表(每一个子类对应一个表的映射策略),那么就需要使用<joined-subclass>元素。

<joined-subclass
	name="ClassName"(1)
	table="tableName" (2)
	proxy="ProxyInterface"(3)
	dynamic-update="true|false"
	dynamic-insert="true|false"
	extends="BaseMappedClassName"> (4)
	
	<key ... >
	
	<property ... / >
	...
<joined-subclass/>
(1)

name: 子类的全限定名。

(2)

table:包含特殊类数据的表名。

(3)

proxy (可选):指定一个类或者接口,在延迟装载时作为代理使用。

(4)

extends (可选):指定这个子类继承的映射类。只有在使用模块化映射文件(Modular Mapping Files) 时才需要被用到。

这种映射策略不需要指定辨别标志(discriminator)字段。但是,每一个都必须使用 <key>元素指定一个表字段包含对象的标识符。

map, set, list, bag

集合类在后面讨论。

引用(import)

假设你的应用程序有两个同样名字的持久化类,但是你不想在NHibernate查询中使用他们的全限定名。除了依赖auto-import="true"以外,类也可以被显式地“import(引用)”。你甚至可以引用没有明确被映射的类和接口。 在需要查询一个特定接口的实现类或者使用一个未映射的类在hql的查询结果中时,这种方式很有用。

<import class="System.Object, System" />
<import 
	class="className" (1)
	rename="newName"  (2)
/>
(1)

class: 任何.NET类的全限定名(或接口)。

(2)

rename (可选 - 默认为类的全限定名): 在查询语句中可以使用的名字。

NHibernate 的类型

实体(Entities)和值(values)

为了理解很多与持久化服务相关的.NET语言级对象的行为,我们需要把它们分为两类:


实体entity 独立于任何持有实体引用的对象。与通常的.NET模型相比,不再被引用的对象会被当作垃圾收集掉。实体必须被显式的保存和删除(除非保存和删除是从父实体向子实体引发的级联)。实体支持循环引用和交叉引用,它们也可以加上版本信息。

实体的持久化状态包含有指向其他实体的连接和一些类型的实例。值是结构、集合、组件或者特定的不可变对象。与实体不同,值(特别是集合和组件)是通过可触及性来进行持久化和删除的。因为值对象(和结构)是随着包含它们的实体而被持久化和删除的,它们不能够被独立的加上版本信息。值没有独立的标识,所以它们不能被两个实体或者集合共享。(译者注:值还应该包含"原始类型",但文档中未提到)

所有的NHibernate类型,除了集合,都支持null语义。

直到现在,我们都一直使用"持久化对象"来代表实体。我们仍然会这么做。然而严格的来说,并不是所有用户定义的,带有持久化状态的类都是实体。组件(component)就是一个用户定义的类,仅仅由值语义构成。

基本值类型(Basic value types)

基本的值类型大致可以粗糙的分为三组:System.ValueType类型,System.Object类型和支持大对象的System.Object类型.和.NET类型一样,System.ValueType对应的列不能存储null值,而System.Object可以.

表4.3 System.ValueType 映射类型

NHibernate 类型.NET 类型Database 类型备注
BooleanSystem.BooleanDbType.Boolean在没有指定类型(type) 属性时的默认值
ByteSystem.ByteDbType.Byte在没有指定类型(type) 属性时的默认值
CharSystem.CharDbType.StringFixedLength - 1 char在没有指定类型(type) 属性时的默认值
DateTimeSystem.DateTimeDbType.DateTime -忽略毫秒在没有指定类型(type) 属性时的默认值
DecimalSystem.DecimalDbType.Decimal在没有指定类型(type) 属性时的默认值
DoubleSystem.DoubleDbType.Double在没有指定类型(type) 属性时的默认值
GuidSystem.GuidDbType.Guid在没有指定类型(type) 属性时的默认值
Int16System.Int16DbType.Int16在没有指定类型(type) 属性时的默认值
Int32System.Int32DbType.Int32在没有指定类型(type) 属性时的默认值
Int64System.Int64DbType.Int64在没有指定类型(type) 属性时的默认值
PersistentEnum一个 System.Enum潜在类型对应的DbType

不用在映射文件指定type="PersistentEnum".而是提供枚举的程序集全名,让NHibernate用反射来猜测类型。枚举使用的潜在类型决定适当的DbType

SingleSystem.SingleDbType.Single在没有指定类型(type) 属性时的默认值
TicksSystem.DateTimeDbType.Int64type="Ticks"必须被指定
TimeSpanSystem.TimeSpanDbType.Int64在没有指定类型(type) 属性时的默认值
TimestampSystem.DateTimeDbType.DateTime - 取决于数据库支持type="Timestamp"必须被指定
TrueFalseSystem.Boolean

DbType.AnsiStringFixedLength - 一个字符,'T' 或者'F'

type="TrueFalse" 必须被指定
YesNoSystem.BooleanDbType.AnsiStringFixedLength - 一个字符,'Y' 或者'N'type="YesNo"必须被指定

表4.4 System.Object 映射类型

NHibernate 类型.NET 类型Database 类型备注
AnsiStringSystem.StringDbType.AnsiStringtype="AnsiString"必须被指定
CultureInfoSystem.Globalization.CultureInfoDbType.String - 表明文化(culture)的5个字符在没有指定类型(type) 属性时的默认值
BinarySystem.Byte[]DbType.Binary在没有指定类型(type) 属性时的默认值
TypeSystem.TypeDbType.String 容纳程序集全名在没有指定类型(type) 属性时的默认值
StringSystem.StringDbType.String在没有指定类型(type) 属性时的默认值

表4.5 Large Object 映射类型

NHibernate 类型.NET 类型Database 类型备注
StringClobSystem.StringDbType.Stringtype="StringClob" 必须被指定.整个字段被读入内存
BinaryBlobSystem.Byte[]DbType.Binarytype="BinaryBlob" 必须被指定. 整个字段被读入内存
Serializable

任何被标记了可序列化属性(SerializableAttribute)的System.Object.

DbType.Binarytype="Serializable" 应该被指定. 如果不能为属性找到NHibernate类型,这是最后可依靠的类型。

要掌握NHibernate或者使用某种工具生成NHibernate的hbm.xml文件,应该了解这是一个NHibernate类型名的完整的层。type="integer"被映射为Int32NHibernateType,type="short"被映射为Int16NHibernateType.查看所有的转换你可以查看NHibernate.Type.TypeFactory类的静态构造函数.

自定义值类型(Custom value types)

开发者创建属于他们自己的值类型也是很容易的。比如说,你可能希望持久化Int64类型的属性,持久化成为VARCHAR字段。NHibernate没有内置这样一种类型。自定义类型能够映射一个属性(或集合元素)到不止一个数据库表字段。比如说,你可能有这样的属性:Name {get; set;} ,这是String类型的,对应的持久化到三个字段:FIRST_NAME, INITIAL, SURNAME

要实现一个自定义类型,可以实现NHibernate.IUserTypeNHibernate.ICompositeUserType中的任一个,并且使用类型的全限定类名来声明属性。请查看NHibernate.DomainModel.DoubleStringType这个例子,看看它是怎么做的。

<property name="TwoStrings" type="NHibernate.DomainModel.DoubleStringType, NHibernate.DomainModel">
	<column name="first_string"/>
	<column name="second_string"/>
</property>

注意使用<column>标签来把一个属性映射到多个字段的做法。

虽然NHibernate内置的丰富类型和对component的支持意味着你可能很少需要使用自定义类型,至少对于你程序中经常出现的自定义类(并非实体)来说,这是一种好方法。比如说,MonetoryAmount(价格总额)对比使用ICompositeUserType来说更好,虽然它可以很容易的使用一个component实现。这样做的动机之一是抽象。通过自定义类型,以后假若你改变表示金额值的方法时,你的映射文件不需要更改,这就得到了保护。

映射到"任意"(any)类型

TODO

SQL中引号包围的标识符

你可强制NHibernate在生成的SQL中把标识符用引号前后包围起来,这需要在映射文档中使用反向引号(`)把表名或者字段名包围(可能比较拗口,请看下面的例子)。NHibernate会使用相应的SQLDialect(方言)来使用正确的引号风格(通常是双引号,但是在SQL Server中是括号,MySQL中是反向引号)。

<class name="LineItem" table="`Line Item`">
	<id name="id" column="`Item Id`">
		<generator class="assigned"/>
	</id>
	<property name="itemNumber" column="`Item #`"/>
	...
</class>

映射文件的模块化(Modular mapping files)

允许在独立的映射文档中定义subclassjoined-subclass,直接位于hibernate-mapping下。这就可以让你每次扩展你的类层次的时候,加入新的映射文件就行了。在子类的映射中你必须指定一个extends属性,指明先前已经映射过的超类。如果你使用嵌入的资源(Embedded Resources)配置NHibernate, hbm.xml文件会自动配置为正确的顺序.如果手动添加或者在cfg.xml文件中指定它们,映射文件的排序是非常重要的!

<hibernate-mapping>
	<subclass name="Eg.Subclass.DomesticCat, Eg" extends="Eg.Cat, Eg" discriminator-value="D">
		<property name="Name" type="String"/>
	</subclass>
</hibernate-mapping>

 

转载于:https://www.cnblogs.com/aaa6818162/archive/2011/05/08/2040200.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值