NHibernate講解


第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的程序. 

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.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM