NHibernate官方文档中文版--基础ORM(Basic O/R Mapping)
映射声明
对象/关系映射在XML文件中配置。mapping文件这样设计是为了使它可读性强并且可修改。mapping语言是以对象为中心,意味着mapping是围绕着持久化类声明来建立的,而不是围绕数据表。
要注意的是,尽管很多NHibernate使用者选择手动定义XML文件,但是仍然有很多工具可以用来生成mapping文件,包括NHibernate.Mapping.Attributes 库和各种各样基于模板的代码生成工具(CodeSmith, MyGeneration)。
让我们用一个mapping的例子作为开始:
<?xml version="1.0"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg" namespace="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>
我们现在讨论mapping文件中的内容。我们将只会描述NHibernate在运行时使用的文档中的标签和特性。mapping文件也包含一些额外的可选的特性和标签,这些特性和标签会影响表结构导出工具(schema export tool)导出的数据库的表结构(database schemas)。(例如not-null特性)。
XML名称空间
所有的XMLmapping应该声明XML名称空间。规定的XML结构定义(schema definition)可以在NHibernate发行版中的src\nhibernate-mapping.xsd 中找到。
小建议:想要启用mapping和配置文件的智能感知功能,需要将相应的.xsd文件作为你解决方案中所有项目的一部分(生成操作可以是‘none’)或者作为“solution files”,或者放在你的“lib”文件夹中,然后将它添加到你的XML文件的schemas属性中。你可以在<VS installation directory>\Xml\Schemas中复制它,要注意你可能得将不同版本的xsd对应不同版本的NHibernate。
hibernate-mapping
这个标签有很多可选的特性。schema特性定义了对应表的数据库表空间。如果指定了相应的表空间,那么数据表名称就会被限定在给定的表空间。如果这个置空,就不会限定表空间。default-cascade 特性定义了没有详细指定级联方式的属性和集合的级联方式。默认情况下,auto-import 特性让我们在查询语句中使用不限定表空间的类名。assembly 和 namespace特性定义了持久化类位于哪个程序集中,在哪个名称空间中声明。
<hibernate-mapping (1) schema="schemaName" (2) default-cascade="none|save-update" (3) auto-import="true|false" (4) assembly="Eg" (5) namespace="Eg" (6) default-access="field|property|field.camecase(7)..." default-lazy="true|false" />
(1) schema (可选): 数据库表空间的名称。
(2) default-cascade (可选 - 默认 none): 默认的级联方式。
(3) auto-import (可选 - 默认true): 定义是否可以在查询语句中使用非限定表空间的类名(在mapping文件中定义的类)。
(4)(5) assembly and namespace(可选): 指定非限定表空间的类所在的程序集和名称空间。
(6) default-access (可选- 默认 property): NHibernate获得属性值的方式
(7) default-lazy (可选- 默认 true):如果设置成false,懒加载就会被禁用
如果你不使用assembly和namespace特性,你就得使用完整的类名,包括程序集和名称空间的名称。
如果你有两个持久化类使用了相同的(未限定表空间的)名字,你应该将auto-import设置成false。如果你试图将两个类设置成相同的名称,那么NHibernate会抛出异常。
类
你可以使用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) select-before-update="true|false" (9) polymorphism="implicit|explicit" (10) where="arbitrary sql where condition" (11) persister="PersisterClass" (12) batch-size="N" (13) optimistic-lock="none|version|dirty|all" (14) lazy="true|false" (15) abstract="true|false" (16) />
(1) name: 持久化类(或者接口)的全限定名。
(2) table(optional - 默认是非限定的类名): 对应的数据库表名。
(3) discriminator-value (optional - 默认是类名): 一个用于区分不同的子类的值,在多态行为时使用。可以设置为null和not null。
(4) mutable (optional, 默认是true): 指定该类的实例可变(不可变)。
(5) schema (optional): 覆盖在根<hibernate-mapping> 标签中指定的表空间名字
(6) proxy (optional): Specifies an interface to use for lazy initializing proxies. You may specify the name of the class itself. 指定一个接口,在延迟装载时作为代理使用。你可以在这里使用该类自己的名字。
(7) dynamic-update (optional, defaults to false): 指定用于UPDATE 的SQL将会在运行时动态生成,并且只更新那些改变过的字段。
(8) dynamic-insert (optional, defaults to false): 指定用于INSERT的SQL将会在运行时动态生成,并且只包含那些非空字段。
(9) select-before-update (optional, defaults to false): 指定NHibernate是否需要执行UPDATE操作,除非它确定对象真的被修改了,不然就不会执行。在一些特定的情况下(实际上仅仅当瞬时态对象要执行update()而被关联到一个新的session的时候),这意味着NHibernate在执行UPDATE之前会执行一个额外的SELECT的SQL语句操作来判断是不是真的需要进行UPDATE。
(10) polymorphism (optional, defaults to implicit): 指定是隐式还是显式的使用查询多态。
(11) where (optional) 指定一个附加的SQL WHERE 条件,在抓取这个类的对象时会一直增加这个条件。
(12) persister (optional): 指定一个自定义的IClassPersister.
(13) batch-size (optional, defaults to 1) 指定通过主键获得实体方式的批量插入量
(14) optimistic-lock (optional, defaults to version): 指定乐观锁策略。
(15) lazy (optional): 如果设置成lazy="false"懒加载会被完全关闭。
(16) abstract (optional): 用来在<union-subclass>继承中标记抽象父类
若指明的持久化类实际上是一个接口,也可以被完美地接受。其后你可以用 <subclass> 来指定该接口的实际实现类名。你可以持久化任何static(静态的)内部类。记得应该使用标准的类名格式,就是说比如:Eg.Foo+Bar 。这是因为HQL解析器的限制,在NHibernate 1.0版本中,在查询中不能使用内部类。
任何对不可变类的修改操作,mutable="false",都不会被持久化。这可以让NHibernate做一些小小的性能优化。
可选的proxy属性可以允许延迟加载类的持久化实例。NHibernate开始会返回实现了这个接口的代理。当代理的某个方法被实际调用的时候,真正的持久化对象才会被加载。参见下面的“用于延迟加载的代理”。
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>元素中可能需要再次设置。这些设置是否能够提高效率要视情形而定。请用你的智慧决定是否使用。
使用select-before-update 通常会降低性能。但是这种方式能够有效地防止不必要的数据库更新。
如果你使用了select-before-update ,你就可以选择一种乐观锁的策略:
version 检查version/timestamp 行
all 检查所有行
dirty 检查改变的行
none 不使用乐观锁
我们强烈建议你使用NHibernate的乐观锁功能的时候使用version/timestamp 。考虑到性能问题,这个是最佳并且唯一的方案来正确地处理在session(例如,当使用ISession.Update() )之外的修改操作。要记住,version或者timestamp属性永远不能为null,无论是哪种unsaved-value 方案,或者实体将要变成游离态。
从NHibernate 1.2.0开始,version的是从1开始,而不是像之前的版本那样从0开始。这个目的是为了让unsaved-value将version设置成0。
子查询
一个其他的映射类的方式是映射一个查询。为了达到这个目的,我们可以使用<subselect>标签,这个标签是独立于<class>, <subclass>, <joined-subclass> 和 <union-subclass>的。subselect标签中的内容是一个SQL查询:
<subselect>
SELECT cat.ID, cat.NAME, cat.SEX, cat.MATE FROM cat
</subselect>
一般来说,当使用subselect来mapping一个查询的时候,你会将类标记成不变的(mutable="false"),除非你使用了自定义的SQL来完成增删改操作。
其次,强制同步受到查询影响的数据表是有意义的,可以使用一个或者多个<synchronize> :
<subselect> SELECT cat.ID, cat.NAME, cat.SEX, cat.MATE FROM cat </subselect> <syncronize table="cat"/>
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 (可选- 默认是 "sensible" ): 一个特定的标识属性值,用来标志该实例是刚刚实例化的(还未保存),以此将这个实例与在之前session中保存或者加载过的瞬时态的实例区分开来。
(5)access (可选- 默认值是property): NHibernate用来访问属性值的策略
如果name属性不存在,会认为这个类没有标识属性。
unsaved-value 属性在NHibernate 1.0中基本不需要。
还有一个另外的<composite-id>声明可以访问旧式的多主键数据。我们强烈不鼓励使用这种方式。
Id生成器
必须要有一个生成器,用来为该持久化类的实例生成唯一的标识。
这个生成器可以使用<generator>子标签来声明。如果这个生成器实例需要某些配置值或者初始化参数,用 <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>
如果没有指定参数,就可以直接在<id> 标签中使用一个generator 特性来声明一个生成器,就像下面的这样:
<id name="Id" type="Int64" column="uid" unsaved-value="0" generator="native" />
所有的生成器实现NHibernate.Id.IIdentifierGenerator接口。这是一个非常简单的接口;某些应用程序可以选择提供他们自己特定的实现。当然,NHibernate提供了很多内置的实现。下面是一些内置生成器的快捷名字:
increment
仅在没有其他进程往同一张数据表中插入数据的时候生成独一无二的整型标识符。不要在一个集群中使用这种生成方式。
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算法
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十六进制算法
<id name="Id" type="String" column="cat_id"> <generator class="uuid.hex"> <param name="format">format_value</param> <param name="separator">separator_value</param> </generator> </id>
UUID是通过调用Guid.NewGuid().ToString(format)产生的。format值的设置请参考MSDN文档。默认的seperator很少也不应该被改变。format决定是否配置好的seperator 能替换format使用的默认seperator。
UUID String Algorithm
UUID是通过调用 Guid.NewGuid().ToByteArray() 并且把 byte[]转换成char[],char[] 做为一个16个字符组成的字符串返回。
GUID Algorithms
guid 标识符通过调用Guid.NewGuid()产生。 为了提升Guids在MS SQL中作为主键,外键和索引的一部分时的性能,可以使用guid.comb。在别的数据库中使用guid.comb的好处是支持非标准的GUID。
标识符字段和序列
对于内部支持标识字段的数据库(DB2,MySQL,Sybase,MS SQL),你可以使用identity关键字生成。对于内部支持序列的数据库(DB2,Oracle, PostgreSQL),你可以使用sequence风格的关键字生成。这两种方式对于插入一个新的对象都需要两次SQL查询。
<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中进行选择,取决于底层数据库的支持能力。
由程序分配的标识符
如果你需要应用程序分配一个标示符(而非NHibernate来生成它们),你可以使用assigned生成器。这种特殊的生成器会使用已经分配给对象的标识符属性的标识符值。用这种特性来分配商业行为的关键字要特别小心(基本上总是一种可怕的设计决定)。
因为其继承天性,使用这种生成器策略的实体不能通过ISession的SaveOrUpdate()方法保存。作为替代,你应该明确告知NHibernate是应该被save还是update,分别调用ISession的Save()或Update()方法。
强化版标识符生成器
Starting with NHibernate release 3.3.0, there are 2 new generators which represent a re-thinking of 2 different aspects of identifier generation. The first aspect is database portability; the second is optimization Optimization means that you do not have to query the database for every request for a new identifier value. These two new generators are intended to take the place of some of the named generators described above, starting in 3.3.x. However, they are included in the current releases and can be referenced by FQN.
The first of these new generators is NHibernate.Id.Enhanced.SequenceStyleGenerator (short name enhanced-sequence) which is intended, firstly, as a replacement for the sequence generator and, secondly, as a better portability generator than native. This is because native generally chooses between identity and sequence which have largely different semantics that can cause subtle issues in applications eyeing portability. NHibernate.Id.Enhanced.SequenceStyleGenerator, however, achieves portability in a different manner. It chooses between a table or a sequence in the database to store its incrementing values, depending on the capabilities of the dialect being used. The difference between this and native is that table-based and sequence-based storage have the same exact semantic. In fact, sequences are exactly what NHibernate tries to emulate with its table-based generators. This generator has a number of configuration parameters:
sequence_name (optional, defaults to hibernate_sequence): the name of the sequence or table to be used.
initial_value (optional, defaults to 1): the initial value to be retrieved from the sequence/table. In sequence creation terms, this is analogous to the clause typically named "STARTS WITH".
increment_size (optional - defaults to 1): the value by which subsequent calls to the sequence/table should differ. In sequence creation terms, this is analogous to the clause typically named "INCREMENT BY".
force_table_use (optional - defaults to false): should we force the use of a table as the backing structure even though the dialect might support sequence?
value_column (optional - defaults to next_val): only relevant for table structures, it is the name of the column on the table which is used to hold the value.
prefer_sequence_per_entity (optional - defaults to false): should we create separate sequence for each entity that share current generator based on its name?
sequence_per_entity_suffix (optional - defaults to _SEQ): suffix added to the name of a dedicated sequence.
optimizer (optional - defaults to none): See Section 5.1.5.8.1, “Identifier generator optimization”
The second of these new generators is NHibernate.Id.Enhanced.TableGenerator (short name enhanced-table), which is intended, firstly, as a replacement for the table generator, even though it actually functions much more like org.hibernate.id.MultipleHiLoPerTableGenerator (not available in NHibernate), and secondly, as a re-implementation of org.hibernate.id.MultipleHiLoPerTableGenerator (not available in NHibernate) that utilizes the notion of pluggable optimizers. Essentially this generator defines a table capable of holding a number of different increment values simultaneously by using multiple distinctly keyed rows. This generator has a number of configuration parameters:
optimizer (optional - defaults to ??): See Section 5.1.5.8.1, “Identifier generator optimization”.
标识符生成器的优化
For identifier generators that store values in the database, it is inefficient for them to hit the database on each and every call to generate a new identifier value. Instead, you can group a bunch of them in memory and only hit the database when you have exhausted your in-memory value group. This is the role of the pluggable optimizers. Currently only the two enhanced generators (Section 5.1.5.8, “Enhanced identifier generators” support this operation.
pooled-lo: similar to pooled, except that it's the starting value of the "current group" that is stored into the database structure. Here, increment_size refers to the values coming from the database.
联合ID
<composite-id name="PropertyName" class="ClassName" unsaved-value="any|none" 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()持久化状态之前,必须填充他的联合属性。我们会在Section 7.4, “Components as composite identifiers”. 中说明一种更加方便的方法,把联合标识实现为一个独立的类,下面描述的属性只对这种备用方法有效:
class (可选 - 默认为通过反射(reflection)得到的属性类型): 作为联合标识的组件类名(参见下一节)。
识别器
在"一棵对象继承树对应一个表"的mapping策略中,<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) formula="arbitrary SQL expressi(5)on" />
(1) column (可选 - 默认为 class) 识别器字段的名字
(2) type (可选 - 默认为 String) 一个NHibernate字段类型的名字
(3) force (可选 - 默认为 false) "强制"NHibernate指定允许的识别器值,就算取得的所有实例都是根类的。
(4) insert (可选 - 默认为 true) 当识别器是被映射的组件的标识符的一部分时设置为false。
(5) formula (可选) an arbitrary SQL expression that is executed when a type has to be evaluated. Allows content-based discrimination. 当一个类型需要被推断的时候执行的一个任意的SQL表达式。允许基于内容的识别。
标识器字段的实际值是根据<class> 和<subclass>元素的discriminator-value得来。
force 特性(仅仅)在表中包含额外的没有映射到持久化对象的识别字段的时候有用。这种情形并不常见。
通过使用formula 特性,你可以声明一个任意的SQL语句用来获得某行的类型:
<discriminator formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end" type="Int32"/>
版本(可选)
<version>标签是可选的,表示这个数据表包含的数据是划分版本的。如果你想要使用长时间事务的时候非常有用(见下文)。
<version column="version_column" (1) name="PropertyName" (2) type="typename" (3) access="field|property|nosetter|ClassName" (4) unsaved-value="null|negative|undefined|value" (5) generated="never|always" (6) />
(1)column (可选 - 默认为属性的名字): 储存版本数字的字段。
(2)name: 持久化类的版本属性名称。
(3)type (可选 - 默认为Int32): 版本数字的类型。
(4)access (可选 - 默认为property): NHibernate获得版本字段值的方法。
(5)unsaved-value (可选 - 默认为 "敏感的" 值): 一个版本属性值,表示一个实体是刚刚实例化的(未保存),将它和在之前的session中保存或者加载的持久化实例区分开来。(undefined 说明了这个标识符属性值应该被使用)
(6)generated (optional - defaults to never): Specifies that this version property value is actually generated by the database. See the discussion of Section 5.5, “Generated Properties”.说明了这个版本属性值是由数据库生成的。可以参见Section 5.5, “Generated Properties”的讨论。
版本数字可以是Int64, Int32, Int16, Ticks, Timestamp, or TimeSpan (或者他们对应的可空值类型)类型。
时间戳(可选)
可选的<timestamp>标签表明这个数据表包含时间戳数据。时间戳可以是版本的一个替代品。时间戳是一种不太安全乐观锁的实现。然而,有些时候应用程序会用其他方式使用时间戳。
<timestamp column="timestamp_column" (1) name="PropertyName" (2) access="field|property|nosetter|Clas(3)sName" unsaved-value="null|undefined|value"(4) generated="never|always" (5) />
(1)column (可选 - 默认为属性的名称): 储存时间戳的字段。
(2)name: .NET的DateTime 类型的属性字段名称。
(3)access (可选 - 默认为 property): NHibernate获得时间戳字段值的方法。
(4)unsaved-value (可选 - 默认为null): 一个时间戳属性值,表示一个实体是刚刚实例化的(未保存),将它和在之前的session中保存或者加载的持久化实例区分开来。(undefined 说明了这个标识符属性值应该被使用)
(5)generated (可选 - 默认为never): 说明了这个时间戳属性值是由数据库生成的。可以参见Section 5.5, “Generated Properties”的讨论。
注意,<timestamp> 和<version type="timestamp">是相同的。
属性
The <property> element declares a persistent property of the class.
<property name="propertyName" (1) column="column_name" (2) type="typename" (3) update="true|false" (4) insert="true|false" (4) formula="arbitrary SQL expression" (5) access="field|property|ClassName" (6) optimistic-lock="true|false" (7) generated="never|insert|always" (8) lazy="true|false" (9) />
(1)name: 类中的属性名称。
(2)column (可选 - 默认为属性的名称): 对应数据表的字段名称
(3)type (可选): 指向NHibernate类型的名称
(4)update, insert (可选 - 默认为 true) : 指定被映射的字段是否被包含在SQL 的UPDATE或者INSERT声明中。将二者都设置成false会得到一个完全的“衍生”的属性,它的值是从其他映射到这个相同的字段或者通过触发器或者通过其他应用程序来初始化
(5)formula (可选 ): 一个定义了一个计算后的属性的SQL语句。这个计算后的属性并没有对应的映射字段。
(6)access (可选 - 默认为property):NHibernate用来获取属性值的策略。
(7)optimistic-lock (可选 - 默认为true): 定义这个属性的更新是否需要获取乐观锁。用其他的话说就是,当这个属性dirty之后要不要变成新的版本。
(8)generated (可选 - 默认为never): 指定这个属性值实际上是由数据库生成的。可以参考Section 5.5, “Generated Properties”的讨论。
(9)lazy (可选 - 默认为false): 指定这个属性是不是懒加载。一个懒加载的属性在实体被初始化加载的时候是不会被加载的,除非关联模式被相应的特定查询重写。当这个实体的懒加载属性被访问的时候,懒加载属性的值才会被加载。
类型名称可以是:
自定义类型名称(例如Illflow.Type.MyCustomType)
要注意的是,如果你使用的是NHibernate 基础类型之外的类型你必须指定完整的程序集限定名称(除非你在<hibernate-mapping>标签中设置了assembly 和 namespace 特性)。
NHibernate 支持.NET 2.0 的可空数据类型。这些类型在代码内几乎都和原本的对应可空类型一样。例如,Nullable<Int32>可以使用type="Int32" 或者 type="System.Int32"来配置映射。
如果你没有指定类型,NHibernate会使用反射来反推出对应的属性类型。NHibernate会按照规则2,3,4的顺序尝试着推测出getter返回类型的名字。然而,这并不足以应付所有情况。在一些情况下,你仍然需要指定type特性(例如,要区分NHibernateUtil.DateTime 和NHibernateUtil.Timestamp或者其他的自定义类型的时候)。
access 特性能够让你控制NHibernate在运行时获得属性值的方式。access 应该是字符形式的access-strategy.naming-strategy。.naming-strategy并不是必须的。
表:access策略
Access 策略名称 |
介绍 |
property | 默认的实现模式。NHibernate使用属性的get/set 方法。使用这种方式的话,就不能使用任何的命名规则,因为name特性的值就是属性的名称。 |
field | NHibernate会直接访问字段。NHibernate使用name特性的值作为字段的名称。当在属性的getter和setter包含了额外的动作,并且你不希望NHibernate在扩充或者获取这个对象的时候触发。如果你希望你的API的使用者使用HQL的时候使用的是属性的名称而不是字段,就要使用这个名称策略。 |
nosetter | 在为属性赋值或者获得属性的值的时候NHibernate会直接访问字段。当你的API使用者不能直接改变属性值,也就是说,一个属性仅仅暴露get访问器的时候,有用。一个名称策略在NHibernate使用name特性作为属性名称并且还需要知道字段名称的时候是需要的。 |
ClassName | 如果NHibernate内置的值获取方式不是你需要的,你可以通过实现NHibernate.Property.IPropertyAccessor接口来自己定义一个。access的值应该是全限定的名称,并且可以通过Activator.CreateInstance(string assemblyQualifiedName)来加载。 |
表:名称策略
名称策略名称 |
介绍 |
camelcase |
name特性转化成驼峰命名方式来查找字段。例如<property name="FooBar" ... >使用字段fooBar。 |
camelcase-underscore |
name特性转化成驼峰命名方式并切加上下划线来查找字段。例如<property name="FooBar" ... >使用字段_fooBar。 |
camelcase-m-underscore |
name特性转化成驼峰命名方式并且以m为前缀,然后加上下划线来查找字段。例如<property name="FooBar" ... >使用字段m_fooBar。 |
lowercase |
name特性转化成了小写字符来查找字段。例如<property name="FooBar" ... >使用字段foobar。 |
lowercase-underscore |
name特性转化成了小写字符并且加上下划线。例如<property name="FooBar" ... >使用字段_foobar |
pascalcase-underscore |
name特性加上下划线来查找字段。例如<property name="FooBar" ... >使用字段_ooBar。 |
pascalcase-m |
name特性以m作为前缀来查找字段。例如<property name="FooBar" ... >使用字段mFooBar。 |
pascalcase-m-underscore |
name特性以m作为前缀,然后加上下划线来查找字段。例如<property name="FooBar" ... >使用字段m_FooBar。 |
多对一关系
一个常见的和其他持久化类的关联可以使用many-to-one 标签。他们的管理是一个多对一的关系。(它实际上就只是一个对象引用)
<many-to-one name="PropertyName" (1) column="column_name" (2) class="ClassName" (3) cascade="all|none|save-update|delete|delete-orphan|(4)all-delete-orphan" fetch="join|select" (5) update="true|false" (6) insert="true|false" (6) property-ref="PropertyNameFromAssociatedClass" (7) access="field|property|nosetter|ClassName" (8) unique="true|false" (9) optimistic-lock="true|false" (10) not-found="ignore|exception" (11) />
(1)name: 类中的属性名称。
(2)column (optional): 对应数据表的字段名称。
(3)class (optional - defaults to the property type determined by reflection): 关联类的名称。
(4)cascade (optional): 指定父对象的哪种操作会级联到关联对象上去。
(5)fetch (optional - defaults to select): 选择outer-join 加载还是顺序选择加载(sequential select fetching)。
(6)update, insert (optional - defaults to true) : 指定被映射的字段是否被包含在SQL 的UPDATE或者INSERT声明中。将二者都设置成false会得到一个完全的“衍生”的属性,它的值是从其他映射到这个相同的字段或者通过触发器或者通过其他应用程序来初始化。
(7)property-ref: (optional) : 关联类的连接到这个外键的属性名称。如果没有指定,就会使用关联类的主键。
(8)access (optional - defaults to property): NHibernate用来获取属性值的策略。
(9)unique (optional): 允许DDL生成的时候对外键字段附加unique限制。
(10)optimistic-lock (optional - defaults to true): 制定对这个属性的更新操作需要或者不需要获得乐观锁。换句话说,当这个属性变脏的时候是否需要更新版本信息。
(11)not-found (optional - defaults to exception): 制定关联外键数据缺失的时候该如何处理:选择ignore会将确实的数据关联成null来处理(译者注:默认情况会报异常)。
cascade特性可以设置成下面的值:all, save-update, delete, none。设置成none之外的其他值都会级联操作关联对象。参见下面的“生命周期对象”。
fetch特性支持两种不同的值:
一个典型的多对一的声明可以简单地这样配置
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
property-ref 特性应该只能被用来映射冗余字段,其外键应该是关联表的一个独一无二的键而不是主键。这是一个丑陋的关系模型。例如,假设Product类有一个独一无二的序列,而这个序列不是主键。(unique特性能够控制NHibernate的DDL生成方式)
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
然后OrderItem的mapping可以是:
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
当然,这还远远不够。
一对一关系
一对一关系可以使用one-to-one 标签声明。
<one-to-one name="PropertyName" (1) class="ClassName" (2) cascade="all|none|save-update|delete|delete-orphan|(3)all-delete-orphan" constrained="true|false" (4) fetch="join|select" (5) property-ref="PropertyNameFromAssociatedClass" (6) access="field|property|nosetter|ClassName" (7) />
(1)name: 类中的属性名称。
(2)class (optional - defaults to the property type determined by reflection): 关联类的名称。
(3)cascade (optional): 指定父对象的哪种操作会级联到关联对象上去。
(4)constrained (optional) specifies that a foreign key constraint on the primary key of the mapped table references the table of the associated class. This option affects the order in which Save() and Delete() are cascaded (and is also used by the schema export tool).
(5)fetch (optional - defaults to select): 选择outer-join 加载还是顺序选择加载(sequential select fetching)。
(6)property-ref: (optional) The name of a property of the associated class that is joined to the primary key of this class. If not specified, the primary key of the associated class is used.
(7)access (optional - defaults to property): NHibernate用来获取属性值的策略。
有两种一对一关联方式:
主键关联不需要额外的字段。如果两条记录通过这种方式关联,那么这两个数据表字段就共享同一个主键值。如果你像要两个对象通过主键关联,那么你必须保证他们的主键值是相同的!
对于主键关联方式,将下面的映射信息分别添加给Employee和Person。
<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实例就会被分配到一个和Employee实例相同的主键值。
你也可以使用一个独一无二的外键来关联Employee和Person,可以简单地这样配置:
<many-to-one name="Person" class="Person" column="PERSON_ID" unique="true"/>
可以通过添加下面的信息到Person的mapping文件中来让这个关联变成一种双向的关联:
<one-to-one name="Employee" class="Employee" property-ref="Person"/>
自然id
<natural-id mutable="true|false"/> <property ... /> <many-to-one ... /> ...... </natural-id>
尽管我们建议使用代理键(surrogate keys )作为主键,你仍然要尝试为所有的实体添加自然键(译者注:原文是:”you should still try to identify natural keys for all entities.“)。自然键是非空且唯一的,可以是属性也可以是多个属性的组合。如果它是不可变的就更好了。可以在natural-id>标签中来配置这个自然键属性。Nhibernate会自动产生这个必要的独一无二的非空约束,然后你的mapping的自描述性就会更强。
我们强烈建议你实现Equals() 和GetHashCode() 方法来比较你实体中的自然键属性。
对于包含自然主键的实体就不需要这个mapping配置了。
组件,dynamic组件
<component>标签将子对象的属性映射到父对象相应的字段上。组件也可能相应地声明他们自己的属性,组件或者集合。参见下面的“组件”。
<component name="PropertyName" (1) class="ClassName" (2) insert="true|false" (3) upate="true|false" (4) access="field|property|nosetter|ClassName" (5) optimistic-lock="true|false"> (6) <property ...../> <many-to-one .... /> ........ </component>
(1)name: 类中的属性名称。
(2)class (optional - defaults to the property type determined by reflection): 组件(子)类的名称。
(3)insert: 映射的字段是否要出现在INSERT语句中。
(4)update: 映射的字段是否要出现在UPDATE语句中。
(5)access (optional - defaults to property): NHibernate用来获取属性值的策略。
(6)optimistic-lock (optional - defaults to true): Specifies 制定对这个属性的更新操作需要或者不需要获得乐观锁。换句话说,当这个属性变脏的时候是否需要更新版本信息。
子<property>标签将其子类映射到表中相应的字段里。
<property>标签允许一个<parent>子标签,这个标签映射这个组件类的一个属性,引用回其包含的实体。
<dynamic-component>标签允许一个IDictionary 对象映射成一个组件,它的属性名称指向字典的key。
属性
<properties>标签允许类的属性命名一个逻辑组合(logical grouping )。最大的用处就是它能够允许一些属性的组合能够成为一个property-ref的配置对象。它也是一种定义由多字段组成的唯一约束(multi-column unique constraint)的简便方式,例如:
<properties name="logicalName" (1) insert="true|false" (2) update="true|false" (3) optimistic-lock="true|false" (4) unique="true|false"> (5) <property .../> <many-to-one .../> ........ </properties>
(1)name: 组合的逻辑名称,它不是一个真正意义上的属性名称。
(3)insert: 映射的字段是否要出现在INSERT语句中。
(4)update: 映射的字段是否要出现在UPDATE语句中。
(6)optimistic-lock (optional - defaults to true): Specifies 制定对这个属性的更新操作需要或者不需要获得乐观锁。换句话说,当这个属性变脏的时候是否需要更新版本信息。
(5)unique (optional - defaults to false): 指定这个组件所有映射的字段是否包含唯一约束。
例如,如果我们有下面这样的<properties> 映射配置:
<class name="Person"> <id name="personNumber" /> <properties name="name" unique="true" update="false"> <property name="firstName" /> <property name="lastName" /> <property name="initial" /> </properties> </class>
你可能有一些冗余的数据关联到Person表的这个唯一键,而不是主键上
<many-to-one name="owner" class="Person" property-ref="name"> <column name="firstName" /> <column name="lastName" /> <column name="initial" /> </many-to-one>
不推荐这种脱离上下文的冗余数据映射方式。
子类
最终,多态对象的持久化许要声明每个父对象类的子类。对于(推荐的)table-per-class-hierarchy 的映射方式,许要使用<subclass>标签。
<subclass name="ClassName" (1) discriminator-value="discriminator_value" (2) proxy="ProxyInterface" (3) lazy="true|false" (4) dynamic-update="true|false" dynamic-insert="true|false"> <property .... /> <properties .... /> ..... </subclass>
(1)name: 全限定的.NET类型名称,包括它的程序集名称。
(2)discriminator-value (optional - defaults to the class name): 用来区分子类的值。
(3)proxy (optional): 指定一个类或者接口来实现懒加载。
(4)lazy (optional, defaults to true): 如果设置成false,懒加载就会被禁用
每个子类都应当声明它自己的属性和子类。<version> 和 <id> 属性会继承自他们的根类(译者注:就是初代祖宗)。继承层次结构中的每个子类都必须定义一个唯一的discriminator-value。如果没有指定的话,那么就要使用完全限定的.NET类名。
想要获得更多关于继承映射的信息,参见继承映射这一章节。
join方式关联的子类
Alternatively, a subclass that is persisted to its own table (table-per-subclass mapping strategy) is declared using a <joined-subclass> element.
<joined-subclass name="ClassName" (1) proxy="ProxyInterface" (2) lazy="true|false" (3) dynamic-update="true|false" dynamic-insert="true|false"> <key .... > <property .... /> <properties .... /> ..... </joined-subclass>
(1)name: The fully qualified class name of the subclass.
(2)proxy (optional): Specifies a class or interface to use for lazy initializing proxies.
(3)lazy (optional): Setting lazy="true" is a shortcut equalivalent to specifying the name of the class itself as the proxy interface.
No discriminator column is required for this mapping strategy. Each subclass must, however, declare a table column holding the object identifier using the <key> element. The mapping at the start of the chapter would be re-written as:
<?xml version="1.0"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg" namespace="Eg"> <class name="Cat" table="CATS"> <id name="Id" column="uid" type="Int64"> <generator class="hilo"/> </id> <property name="BirthDate" type="Date"/> <property name="Color" not-null="true"/> <property name="Sex" not-null="true"/> <property name="Weight"/> <many-to-one name="Mate"/> <set name="Kittens"> <key column="MOTHER"/> <one-to-many class="Cat"/> </set> <joined-subclass name="DomesticCat" table="DOMESTIC_CATS"> <key column="CAT"/> <property name="Name" type="String"/> </joined-subclass> </class> <class name="Dog"> <!-- mapping for Dog could go here --> </class> </hibernate-mapping>
For information about inheritance mappings, see Chapter 8, Inheritance Mapping.
union方式关联的子类
A third option is to map only the concrete classes of an inheritance hierarchy to tables, (the table-per-concrete-class strategy) where each table defines all persistent state of the class, including inherited state. In NHibernate, it is not absolutely necessary to explicitly map such inheritance hierarchies. You can simply map each class with a separate <class> declaration. However, if you wish use polymorphic associations (e.g. an association to the superclass of your hierarchy), you need to use the <union-subclass> mapping.
<union-subclass name="ClassName" (1) table="tablename" (2) proxy="ProxyInterface" (3) lazy="true|false" (4) dynamic-update="true|false" dynamic-insert="true|false" schema="schema" catalog="catalog" extends="SuperclassName" abstract="true|false" persister="ClassName" subselect="SQL expression" entity-name="EntityName" node="element-name"> <property .... /> <properties .... /> ..... </union-subclass>
(1)name: The fully qualified class name of the subclass.
(2)table: The name of the subclass table.
(3)proxy (optional): Specifies a class or interface to use for lazy initializing proxies.
(4)lazy (optional, defaults to true): Setting lazy="false" disables the use of lazy fetching.
No discriminator column or key column is required for this mapping strategy.
For information about inheritance mappings, see Chapter 8, Inheritance Mapping.
join
Using the <join> element, it is possible to map properties of one class to several tables, when there's a 1-to-1 relationship between the tables.
<join table="tablename" (1) schema="owner" (2) fetch="join|select" (3) inverse="true|false" (4) optional="true|false"> (5) <key ... /> <property ... /> ... </join>
(1)table: The name of the joined table.
(2)schema (optional): Override the schema name specified by the root <hibernate-mapping> element.
(3)fetch (optional - defaults to join): If set to join, the default, NHibernate will use an inner join to retrieve a <join> defined by a class or its superclasses and an outer join for a <join> defined by a subclass. If set to select then NHibernate will use a sequential select for a <join> defined on a subclass, which will be issued only if a row turns out to represent an instance of the subclass. Inner joins will still be used to retrieve a <join> defined by the class and its superclasses.
(4)inverse (optional - defaults to false): If enabled, NHibernate will not try to insert or update the properties defined by this join.
(5)optional (optional - defaults to false): If enabled, NHibernate will insert a row only if the properties defined by this join are non-null and will always use an outer join to retrieve the properties.
For example, the address information for a person can be mapped to a separate table (while preserving value type semantics for all properties):
<class name="Person" table="PERSON"> <id name="id" column="PERSON_ID">...</id> <join table="ADDRESS"> <key column="ADDRESS_ID"/> <property name="address"/> <property name="zip"/> <property name="country"/> </join> ...
This feature is often only useful for legacy data models, we recommend fewer tables than classes and a fine-grained domain model. However, it is useful for switching between inheritance mapping strategies in a single hierarchy, as explained later.
map, set, list, bag
集合在之后的章节介绍。
import
Suppose your application has two persistent classes with the same name, and you don't want to specify the fully qualified name in NHibernate queries. Classes may be "imported" explicitly, rather than relying upon auto-import="true". You may even import classes and interfaces that are not explicitly mapped.
<import class="System.Object" rename="Universe"/>
<import class="ClassName" (1) rename="ShortName" (2) />
(1)class: The fully qualified class name of any .NET class, including its assembly name.
(2)rename (optional - defaults to the unqualified class name): A name that may be used in the query language.
NHibernate的类型
实体和值
想要明白各种关于持久化服务的.NET 语言级别对象,我们需要把他们区分成以下两种类型:
实体和其他与实体关联的对象独立。和实体相反,普通的.NET模型的关联会被GC回收。实体必须被显式地保存和删除(除了父类的级联保存和删除)。这个和依赖可获得性(reachability)来持久化的ODMG模型对象不同——并且相应的更接近在大系统中使用应用程序对象的方式。实体支持循环和共享的引用。它们也是有版本的。
实体的持久化状态包括了对其他实体的引用和值类型实例,值和基类,集合,组件和特定的不变对象。和实体不同,值类型(尤其是集合和组件)依赖可获得性(reachability)来进行持久化和删除。因为值类型对象(和基类)的持久化和删除都通过他们包含的实体,因此他们不需要独立的版本信息。值类型没有独立的标识符,因此他们不能够被两个实体或者集合共享。
对于.NET的可空类型(例如,不继承自System.ValueType)所有的NHibernate类型,除了集合,都支持null语法。
到这里位置,我们使用“持久化类”来指实体。我们将会继续这么做。严格来说,并不是所有的用户自定义的包含持久化状态的类都是实体。组件是一个包含值类型语法的自定义类型。
基础值类型
基础值类型主要分成三类——System.ValueType 类型, System.Object 类型, 和 System.Object 类型。和.NET类型一样,System.ValueType 的值类型不能保存null值,而System.Object 类型可以为null。
System.ValueType映射类型
NHibernate类型 |
.NET类型 |
数据库类型 |
备注 |
AnsiChar |
System.Char |
DbType.AnsiStringFixedLength - 1 char |
|
Boolean |
System.Boolean |
DbType.Boolean |
Default when no type attribute specified. |
Byte |
System.Byte |
DbType.Byte |
Default when no type attribute specified. |
Char |
System.Char |
DbType.StringFixedLength - 1 char |
Default when no type attribute specified. |
Date |
System.DateTime |
DbType.Date |
type="Date" must be specified. |
DateTime |
System.DateTime |
DbType.DateTime - ignores the milliseconds |
Default when no type attribute specified. |
DateTime2 |
System.DateTime |
DbType.DateTime2 |
type="DateTime2" must be specified. |
DbTimestamp |
System.DateTime |
DbType.DateTime - as specific as database supports. |
type="DbTimestamp" must be specified. When used as a version field, uses the database's current time rather than the client's current time. |
LocalDateTime |
System.DateTime |
DbType.DateTime - ignores the milliseconds |
Ensures the DateTimeKind is set to DateTimeKind.Utc |
UtcDateTime |
System.DateTime |
DbType.DateTime - ignores the milliseconds |
Ensures the DateTimeKind is set to DateTimeKind.Utc |
Decimal |
System.Decimal |
DbType.Decimal |
Default when no type attribute specified. |
Double |
System.Double |
DbType.Double |
Default when no type attribute specified. |
Guid |
System.Guid |
DbType.Guid |
Default when no type attribute specified. |
Int16 |
System.Int16 |
DbType.Int16 |
Default when no type attribute specified. |
Int32 |
System.Int32 |
DbType.Int32 |
Default when no type attribute specified. |
Int64 |
System.Int64 |
DbType.Int64 |
Default when no type attribute specified. |
PersistentEnum |
A System.Enum |
The DbType for the underlying value. |
Do not specify type="PersistentEnum" in the mapping. Instead specify the Assembly Qualified Name of the Enum or let NHibernate use Reflection to "guess" the Type. The UnderlyingType of the Enum is used to determine the correct DbType. |
Single |
System.Single |
DbType.Single |
Default when no type attribute specified. |
Ticks |
System.DateTime |
DbType.Int64 |
type="Time" must be specified. |
Time |
System.DateTime |
DbType.Time |
type="Time" must be specified. |
TimeAsTimeSpan |
System.TimeSpan |
DbType.Time |
type="TimeAsTimeSpan" must be specified. |
TimeSpan |
System.TimeSpan |
DbType.Int64 |
Default when no type attribute specified. |
Timestamp |
System.DateTime |
DbType.DateTime - as specific as database supports. |
type="Timestamp" must be specified. |
TrueFalse |
System.Boolean |
DbType.AnsiStringFixedLength - 1 char either 'T' or 'F' |
type="TrueFalse" must be specified. |
YesNo |
System.Boolean |
DbType.AnsiStringFixedLength - 1 char either 'T' or 'F' |
type="YesNo" must be specified. |
System.Object映射类型
NHibernate类型 |
.NET类型 |
数据库类型 |
备注 |
AnsiString |
System.String |
DbType.AnsiString |
type="AnsiString" must be specified. |
CultureInfo |
System.Globalization.CultureInfo |
DbType.String - 5 chars for culture |
Default when no type attribute specified. |
Binary | System.Byte[] | DbType.Binary | Default when no type attribute specified. |
Type | System.Type | DbType.String holding Assembly Qualified Name. | Default when no type attribute specified. |
String | System.String | DbType.String |
Default when no type attribute specified. |
Object映射类型
NHibernate类型 |
.NET类型 |
数据库类型 |
备注 |
StringClob | ystem.String |
DbType.String |
type="StringClob" must be specified. Entire field is read into memory. |
BinaryBlob |
System.Byte[] |
DbType.Binary |
type="BinaryBlob" must be specified. Entire field is read into memory. |
Serializable |
Any System.Object that is marked with SerializableAttribute. |
DbType.Binary |
type="Serializable" should be specified. This is the fallback type if no NHibernate Type can be found for the Property. |
Nhibernate支持一些额外的类型名称来兼容Java的Hibernate()type="integer"或者type="int"都会映射到Int32 的NHibernate类型,type="short"会映射到Int16的NHibernate类型。想要查阅所有的转化规则,你可以看源码中NHibernate.Type.TypeFactory类的静态构造函数。
自定义值类型
It is relatively easy for developers to create their own value types. For example, you might want to persist properties of type Int64 to VARCHAR columns. NHibernate does not provide a built-in type for this. But custom types are not limited to mapping a property (or collection element) to a single table column. So, for example, you might have a property Name { get; set; } of type String that is persisted to the columns FIRST_NAME, INITIAL, SURNAME.
To implement a custom type, implement either NHibernate.UserTypes.IUserType or NHibernate.UserTypes.ICompositeUserType and declare properties using the fully qualified name of the type. Check out NHibernate.DomainModel.DoubleStringType to see the kind of things that are possible.
<property name="TwoStrings" type="NHibernate.DomainModel.DoubleStringType, NHibernate.DomainModel"> <column name="first_string"/> <column name="second_string"/> </property>
Notice the use of <column> tags to map a property to multiple columns.
The ICompositeUserType, IEnhancedUserType, INullableUserType, IUserCollectionType, and IUserVersionType interfaces provide support for more specialized uses.
You may even supply parameters to an IUserType in the mapping file. To do this, your IUserType must implement the NHibernate.UserTypes.IParameterizedType interface. To supply parameters to your custom type, you can use the <type> element in your mapping files.
<property name="priority"> <type name="MyCompany.UserTypes.DefaultValueIntegerType"> <param name="default">0</param> </type> </property>
The IUserType can now retrieve the value for the parameter named default from the IDictionary object passed to it.
If you use a certain UserType very often, it may be useful to define a shorter name for it. You can do this using the <typedef> element. Typedefs assign a name to a custom type, and may also contain a list of default parameter values if the type is parameterized.
<typedef class="MyCompany.UserTypes.DefaultValueIntegerType" name="default_zero"> <param name="default">0</param> </typedef>
<property name="priority" type="default_zero"/>
It is also possible to override the parameters supplied in a typedef on a case-by-case basis by using type parameters on the property mapping.
Even though NHibernate's rich range of built-in types and support for components means you will very rarely need to use a custom type, it is nevertheless considered good form to use custom types for (non-entity) classes that occur frequently in your application. For example, a MonetaryAmount class is a good candidate for an ICompositeUserType, even though it could easily be mapped as a component. One motivation for this is abstraction. With a custom type, your mapping documents would be future-proofed against possible changes in your way of representing monetary values.
任意类型mapping
There is one further type of property mapping. The <any> mapping element defines a polymorphic association to classes from multiple tables. This type of mapping always requires more than one column. The first column holds the type of the associated entity. The remaining columns hold the identifier. It is impossible to specify a foreign key constraint for this kind of association, so this is most certainly not meant as the usual way of mapping (polymorphic) associations. You should use this only in very special cases (eg. audit logs, user session data, etc).
<any name="AnyEntity" id-type="Int64" meta-type="Eg.Custom.Class2TablenameType"> <column name="table_name"/> <column name="id"/> </any>
The meta-type attribute lets the application specify a custom type that maps database column values to persistent classes which have identifier properties of the type specified by id-type. If the meta-type returns instances of System.Type, nothing else is required. On the other hand, if it is a basic type like String or Char, you must specify the mapping from values to classes.
<any name="AnyEntity" id-type="Int64" meta-type="String"> <meta-value value="TBL_ANIMAL" class="Animal"/> <meta-value value="TBL_HUMAN" class="Human"/> <meta-value value="TBL_ALIEN" class="Alien"/> <column name="table_name"/> <column name="id"/> </any>
<any name="PropertyName" (1) id-type="idtypename" (2) meta-type="metatypename" (3) cascade="none|all|save-update" (4) access="field|property|nosetter|ClassName" (5) optimistic-lock="true|false" (6) > <meta-value ... /> <meta-value ... /> ..... <column .... /> <column .... /> ..... </any>
(1)name: the property name.
(2)id-type: the identifier type.
(3)meta-type (optional - defaults to Type): a type that maps System.Type to a single database column or, alternatively, a type that is allowed for a discriminator mapping.
(4)cascade (optional - defaults to none): the cascade style.
(5)access (optional - defaults to property): The strategy NHibernate should use for accessing the property value.
(6)optimistic-lock (optional - defaults to true): Specifies that updates to this property do or do not require acquisition of the optimistic lock. In other words, define if a version increment should occur if this property is dirty.
SQL引用标识符
You may force NHibernate to quote an identifier in the generated SQL by enclosing the table or column name in backticks in the mapping document. NHibernate will use the correct quotation style for the SQL Dialect (usually double quotes, but brackets for SQL Server and backticks for MySQL).
<class name="LineItem" table="`Line Item`"> <id name="Id" column="`Item Id`"/><generator class="assigned"/></id> <property name="ItemNumber" column="`Item #`"/> ... </class>
模块mapping文件
It is possible to define subclass and joined-subclass mappings in saparate mapping documents, directly beneath hibernate-mapping. This allows you to extend a class hierachy just by adding a new mapping file. You must specify an extends attribute in the subclass mapping, naming a previously mapped superclass. Use of this feature makes the ordering of the mapping documents important!
<hibernate-mapping> <subclass name="Eg.Subclass.DomesticCat, Eg" extends="Eg.Cat, Eg" discriminator-value="D"> <property name="name" type="string"/> </subclass> </hibernate-mapping>
生成的属性
Generated properties are properties which have their values generated by the database. Typically, NHibernate applications needed to Refresh objects which contain any properties for which the database was generating values. Marking properties as generated, however, lets the application delegate this responsibility to NHibernate. Essentially, whenever NHibernate issues an SQL INSERT or UPDATE for an entity which has defined generated properties, it immediately issues a select afterwards to retrieve the generated values.
Properties marked as generated must additionally be non-insertable and non-updateable. Only Section 5.1.8, “version (optional)”, Section 5.1.9, “timestamp (optional)”, and Section 5.1.10, “property” can be marked as generated.
never (the default) - means that the given property value is not generated within the database.
insert - states that the given property value is generated on insert, but is not regenerated on subsequent updates. Things like created-date would fall into this category. Note that even though Section 5.1.8, “version (optional)” and Section 5.1.9, “timestamp (optional)” properties can be marked as generated, this option is not available there...
always - states that the property value is generated both on insert and on update.
辅助的数据库对象
Allows CREATE and DROP of arbitrary database objects, in conjunction with NHibernate's schema evolution tools, to provide the ability to fully define a user schema within the NHibernate mapping files. Although designed specifically for creating and dropping things like triggers or stored procedures, really any SQL command that can be run via a IDbCommand.ExecuteNonQuery() method is valid here (ALTERs, INSERTS, etc). There are essentially two modes for defining auxiliary database objects.
The first mode is to explicitly list the CREATE and DROP commands out in the mapping file:
<nhibernate-mapping> ... <database-object> <create>CREATE TRIGGER my_trigger ...</create> <drop>DROP TRIGGER my_trigger</drop> </database-object> </nhibernate-mapping>
The second mode is to supply a custom class which knows how to construct the CREATE and DROP commands. This custom class must implement the NHibernate.Mapping.IAuxiliaryDatabaseObject interface.
<hibernate-mapping> ... <database-object> <definition class="MyTriggerDefinition, MyAssembly"/> </database-object> </hibernate-mapping>
You may also specify parameters to be passed to the database object:
<hibernate-mapping> ... <database-object> <definition class="MyTriggerDefinition, MyAssembly"> <param name="parameterName">parameterValue</param> </definition> </database-object> </hibernate-mapping>
NHibernate will call IAuxiliaryDatabaseObject.SetParameterValues passing it a dictionary of parameter names and values.
Additionally, these database objects can be optionally scoped such that they only apply when certain dialects are used.
<hibernate-mapping> ... <database-object> <definition class="MyTriggerDefinition"/> <dialect-scope name="NHibernate.Dialect.Oracle9iDialect"/> <dialect-scope name="NHibernate.Dialect.Oracle8iDialect"/> </database-object> </hibernate-mapping>