英文原文地址:https://github.com/jagregory/fluent-nhibernate/wiki/Getting-started
翻譯原文地址:http://www.cnblogs.com/13yan/p/5685307.html
入門指南
Fluent NHibernate 概述
Fluent NHibernate 提供一個替代 NHibernate 的標准 XML 映射文件的方法。而不是編寫 XML 文檔 (.hbm.xml 文件),Fluent NHibernate 可以讓你在強類型 C# 代碼中編寫映射。這會易於重構、提高可讀性並且會有更簡潔的代碼。
Fluent NHibernate 也有幾個其他功能,包括︰
- 自動映射-根據您的實體設計推斷映射在哪里;(https://github.com/jagregory/fluent-nhibernate/wiki/Auto-mapping)
- 持久化規范測試-往返測試您的實體,而永遠不必再寫 CRUD 的線(https://github.com/jagregory/fluent-nhibernate/wiki/Persistence-specification-testing)
- 完整的應用程序配置與我們Fluent configuration API(https://github.com/jagregory/fluent-nhibernate/wiki/Fluent-configuration)
- 數據庫配置-用代碼流利地配置您的數據庫(https://github.com/jagregory/fluent-nhibernate/wiki/Database-configuration)
Fluent NHibernate 於NHibernate 核心的外部,但完全兼容NHibernate 2.1。
背景
NHibernate 是一個對象關系映射框架,它 (作為 ORM家族) 映射關系型數據庫中的數據和對象。它定義的映射內容為XML格式,稱為 HBM,每個類都有一個相應的 HBM XML 文件,將它映射到數據庫中的特定結構。Fluent NHibernate 將替代這些映射文件。
為什么取代 HBM.XML 呢?雖然 XML 和代碼分離是好的,但它會導致幾種不良的情況。
- 由於 XML 還沒有被編譯器評估,您可以在您的類中重命名屬性,但映射文件不會被重構工具同步更新;在這種情況下,直到映射在運行時解析,IDE都不會給你錯誤提示。
- XML 是冗長的;雖然NHibernate 已逐漸減少強制性的 XML 元素,但你仍然不能擺脫冗長的 XML。
- 重復映射-NHibernate HBM 映射可以變得相當詳細,如果你發現自己一遍遍指定相同的規則。例如,如果您需要確保所有字符串屬性不能為 null,應該有 1000長度和所有 int必須有一個-1的默認值。
Fluent NHibernate 怎樣解決這些問題?它是通過移動您的映射到實際的代碼,所以它們是隨着您的應用程序一起編譯的; 使用重構工具重命名實體也將同時更新您的映射,任何拼寫錯誤都將編譯失敗。如何應對重復,Fluent NHibernate 具有常規配置系統,你可以在某個地方指定模式重寫命名約定和許多其他東西;你設置一次,應該如何命名這東西,然后Fluent NHibernate會重置它們。
簡單的例子
這里是一個簡單的例子,前提是默認你已經會使用NHibernate。
傳統的 HBM XML 映射
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="QuickStart" assembly="QuickStart">
<class name="Cat" table="Cat">
<id name="Id">
<generator class="identity" />
</id>
<property name="Name">
<column name="Name" length="16" not-null="true" />
</property>
<property name="Sex" />
<many-to-one name="Mate" />
<bag name="Kittens">
<key column="mother_id" />
<one-to-many class="Cat" />
</bag>
</class>
</hibernate-mapping>
Fluent NHibernate 映射
public class CatMap : ClassMap<Cat>
{
public CatMap()
{
Id(x => x.Id);
Map(x => x.Name)
.Length(16)
.Not.Nullable();
Map(x => x.Sex);
References(x => x.Mate);
HasMany(x => x.Kittens);
}
}
安裝
二進制文件
盡可能使用NuGet獲取可用的版本。請參見︰ Fluent NHibernate NuGet 包。
PM> Install-Package FluentNHibernate
從我們的 CI 服務器(teamcity.codebetter.com),每次成功生成一個短時期也有二進制文件。
獲取源碼
我們的源代碼是 Git 使用 Github,你有兩個選項可用。如果你打算修改Fluent NHibernate 和貢獻回給我們,然后你最好forking我們的倉儲,並向我們發送pull請求。你可以了解更多有關,在 github 指南網站︰ forking和pull的請求。另一個選項是直接克隆的我們的倉儲,但這將讓你處於一種尷尬的狀態,如果你打算作出貢獻的話 (如果你不打算修改並貢獻給我們就沒關系)。
我們的倉儲位於︰ http://github.com/jagregory/fluent-nhibernate
再次,我們推薦forking我們倉儲,如果你不想要這樣做,你可以做一個直接克隆︰
git clone git://github.com/jagregory/fluent-nhibernate.git
一旦你得到一份拷貝在本地計算機上,有兩種方法你可以生成。
- 如果您有安裝 Ruby︰ 第一次生成時,您應該運行 InstallGems.bat 從Fluent NHibernate 根目錄下;這將確保您的機器已成功生成所需的所有的gems。一旦完成,並為所有后續生成,您需要運行 Build.bat;此批處理文件運行我們的rake腳本,生成標准的 NHibernate 2.0 版本和輸出中生成目錄。生成的更多選項,請參閱我們rake腳本的詳細信息。
- 沒有Ruby︰ 打開了在 Visual Studio 生成 src\FluentNHibernate.sln 解決方案,您也可以選擇可以運行測試。
現在,你得Fluent NHibernate 的生成,你只需要在您的項目中引用 FluentNHibernate.dll 程序集。
譯者的話:其實一般使用的話沒有這么麻煩,我們只要下載DLL並在項目中引用即可,原文中就推薦用NuGet來獲取,也可以在官網下載。
你的第一個項目
所有源碼可以在主要的Fluent NHibernate解決方案中,在 Example.FirstProject 項目中找到。
(https://github.com/jagregory/fluent-nhibernate/tree/master/src/Examples.FirstProject)
譯者的話:這就是源代碼和本示例項目的下載地址。
你需要有Fluent NHibernate 已經下載並編譯要遵循本指南中,如果您沒有這樣做,但然后請參考上面的安裝一節。
本例中我們要將映射一個簡單領域模型為一家零售公司。公司擁有幾個商店,每個產品(一些產品在幾個店都有,一些是某個商店獨有),和每個員工。在數據庫術語中,這是Store、 Employee和Product 表,Store表與Product表之間多對多關系。

首先,創建一個控制台應用程序並引用你先前編譯的 FluentNHibernate.dll, 和無論哪個版本的 NHibernate.dll (如果你不確定,只是使用源碼中輸出的FluentNHibernate.dll);此外,因為本例中我們要使用 SQLite 數據庫,您需要分別引用Fluent NHibernate 和 System.Data.SQLite 類庫。
你可以看到我習慣於左邊的項目結構。Entities文件夾是您實際的領域對象,而我們要把你的Fluent Map類放在Mappings文件夾里。
本指南的剩下部分,我假設你已經使用與我相同的項目結構。
實體
現在,我們已有項目的格局,讓我們開始創建我們的實體。我們有三張表,我們需要映射 (我們現在忽略多對多關聯),所以這是每個表建一個實體。在Entities文件夾中創建下面的類。
public class Employee
{
public virtual int Id { get; protected set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual Store Store { get; set; }
}
我們的Employee實體中有一個Id,這個人的姓名 (名字和姓氏的分開)和最后對他們工作所在的商店Store的引用。
還有兩件事,如果你熟悉 NHibernate 可能站出來給你。首先,該 Id 屬性具有protected的set訪問器,這是因為它是只應在NHibernate設置該 Id 的值。其次,所有的屬性都標記為虛virtual的;這是因為 NHibernate 會在運行時創建您的實體的"代理者",為了允許延遲加載,在代理類中需要重載這些屬性。
譯者的話:雖然這段話翻譯的不怎么好,但這些都是NHibernate里的內容,在我的博客中也單獨解釋過為什么使用virtual關鍵字。
public class Product
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual double Price { get; set; }
public virtual IList<Store> StoresStockedIn { get; protected set; }
public Product()
{
StoresStockedIn = new List<Store>();
}
}
Product具有 Id、 它的Name、 Price和陳列的Store的集合。
public class Store
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual IList<Product> Products { get; set; }
public virtual IList<Employee> Staff { get; set; }
public Store()
{
Products = new List<Product>();
Staff = new List<Employee>();
}
public virtual void AddProduct(Product product)
{
product.StoresStockedIn.Add(this);
Products.Add(product);
}
public virtual void AddEmployee(Employee employee)
{
employee.Store = this;
Staff.Add(employee);
}
}
最后,我們有我們的Store 實體。這有一個 Id 和Name,以及,Product的集合,以及在那里工作的Employee(在工作人員名單)的集合。此實體包含一點點的邏輯,可以使我們的代碼更簡單,就是兩個叫 AddProduct 和 AddEmployee 的方法;這些方法用於將項添加到集合,並設置另一側的關系。
如果你是NHibernate老手,然后你就會很熟悉這些;然而,如果是新手,需要讓我來解釋一下︰ NHibernate 它將正確保存之前,需要您設置雙方映射關系。沒有這額外的代碼,無處不在它已減少到這些額外的方法到商店中。
譯者的話:這段話的意思是,我們為商店增加一個商品,需要在它的Product集合中增加一個項,同時也要在另一邊(Product類中,商品陳列的商店)加入關系。
映射
現在,我們已經得到了我們創建的實體,是時候映射它們,引用Fluent NHibernate。我們將開始與最簡單的類——Employee。所有以下映射應該創建在Mappings文件夾內。
若要映射一個實體,您必須創建一個專用的映射類 (通常沿用 EntityNameMap 的命名約定),所以我們將創建一個EmployeeMap類;這些類映射必須繼承 ClassMap,泛型 T 表示您的實體。
public class EmployeeMap : ClassMap<Employee>
{
}
所以我們需要添加一個構造函數,構造函數內完成自己的映射。
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.Id);
}
}
首先我們映射 Id 列,並告訴Fluent NHibernate 實際上它是標識(唯一主鍵)。在此示例中的 x 是Employee 的Fluent NHibernate 使用檢索屬性的詳細信息,所以在這里你真正要做的事告訴它哪個屬性是Id的一個實例。Fluent NHibernate 會看到你的 Id 屬性的類型為 int,它會自動決定應該將其映射作為自動遞增的標識在數據庫-派上用場!
NHibernate 對於用戶來說,這意味着它會自動創建生成器元素作為標識。
讓我們來映射其余的Employee。
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.Id);
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Store);
}
}
在那里我們使用了幾個新的方法,Map和References; Map創建一個簡單屬性的映射,而References創建兩個實體之間的多對一關系。在這種情況下我們映射名字和姓氏作為簡單的屬性,並創建多對一關系,關聯到Store (多個Employee對應一個Store) 通過Store屬性。
NHibernate 用戶︰ Map相當於屬性元素,References是多對一。
讓我們繼續映射Store。
public class StoreMap : ClassMap<Store>
{
public StoreMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Staff)
.Inverse()
.Cascade.All();
HasManyToMany(x => x.Products)
.Cascade.All()
.Table("StoreProduct");
}
}
再次,是幾個新的調用。如果你記得之前的Employee,我們創建一個多對一關系關聯到Store,那么現在,我們正在映射Store我們可以創建這種關系的另一邊。所以HasMany是創建一個Employee 的一對多關系(一個Store對應多個Employee),是Employee.Store 關系的另一面創建一個一對多關系。其他新的方法是HasManyToMany,創建與Product的多對多關系。
這也只是你初嘗得到了fluent的接口提供了Fluent NHibernate。HasMany方法具有第二個調用直接從它的返回類型 (Inverse()) 和 HasManyToMany 有 Cascade.All() 和表;這是調用方法鏈,它用來在您的配置中創建更多的自然語言。
- HasMany上的Inverse是一個NHibernate項,它意味着關系的另一端是負責保存。
- HasManyToMany上的Cascade.All 告訴 NHibernate 級聯到實體的事件集合。(所以當你保存Store,所有Product也會保存)
- Table 是設置多對多關系聯接表的名稱。
如果你做一種雙向多對多,Table調用目前是必須,因為Fluent NHibernate 目前不能猜出名稱應該是什么;對於所有其他關聯並不是必需。
NHibernaters︰ HasMany 默認映射到一個bag ,和one-to-many元素里面; HasManyToMany 是相同,但具有many-to-many的元素。
最后,讓我們映射的Product。
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.Price);
HasManyToMany(x => x.StoresStockedIn)
.Cascade.All()
.Inverse()
.Table("StoreProduct");
}
}
這是Product映射;在這種情況下,我們使用了只有我們已經遇到過的方法。HasManyToMany 設置與Store的雙向多對多關系的另一邊。
應用程序
在本節中我們將初始化一些數據,並輸出到控制台。
static void Main()
{
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
// create a couple of Stores each with some Products and Employees
var barginBasin = new Store { Name = "Bargin Basin" };
var superMart = new Store { Name = "SuperMart" };
var potatoes = new Product { Name = "Potatoes", Price = 3.60 };
var fish = new Product { Name = "Fish", Price = 4.49 };
var milk = new Product { Name = "Milk", Price = 0.79 };
var bread = new Product { Name = "Bread", Price = 1.29 };
var cheese = new Product { Name = "Cheese", Price = 2.10 };
var waffles = new Product { Name = "Waffles", Price = 2.41 };
var daisy = new Employee { FirstName = "Daisy", LastName = "Harrison" };
var jack = new Employee { FirstName = "Jack", LastName = "Torrance" };
var sue = new Employee { FirstName = "Sue", LastName = "Walkters" };
var bill = new Employee { FirstName = "Bill", LastName = "Taft" };
var joan = new Employee { FirstName = "Joan", LastName = "Pope" };
// add products to the stores, there's some crossover in the products in each
// store, because the store-product relationship is many-to-many
AddProductsToStore(barginBasin, potatoes, fish, milk, bread, cheese);
AddProductsToStore(superMart, bread, cheese, waffles);
// add employees to the stores, this relationship is a one-to-many, so one
// employee can only work at one store at a time
AddEmployeesToStore(barginBasin, daisy, jack, sue);
AddEmployeesToStore(superMart, bill, joan);
// save both stores, this saves everything else via cascading
session.SaveOrUpdate(barginBasin);
session.SaveOrUpdate(superMart);
transaction.Commit();
}
// retreive all stores and display them
using (session.BeginTransaction())
{
var stores = session.CreateCriteria(typeof(Store))
.List<Store>();
foreach (var store in stores)
{
WriteStorePretty(store);
}
}
Console.ReadKey();
}
}
public static void AddProductsToStore(Store store, params Product[] products)
{
foreach (var product in products)
{
store.AddProduct(product);
}
}
public static void AddEmployeesToStore(Store store, params Employee[] employees)
{
foreach (var employee in employees)
{
store.AddEmployee(employee);
}
}
為簡潔起見,我遺漏了的只是將各種關系的 Console.Write 呼吁在Store 的 WriteStorePretty 定義 (但你可以看到它在完整的代碼)。
這是來自你 Program.cs 的Main 方法。它是有點長,但我們在做創建一組Store實例,然后將一些Employee和Product添加到它們,然后保存;最后,它重新從數據庫中查詢他們,並把它們寫出到控制台。
你將無法運行這一步,因為還有一事要做。我們需要實現的 CreateSessionFactory 方法;這是我們配置的去向,NHibernate 和Fluent NHibernate 相互配合。
配置
讓我們實現的 CreateSessionFactory 方法。
private static ISessionFactory CreateSessionFactory()
{
}
這是排序的方法簽名,你會注意到它返回 NHibernate ISessionFactory。現在我們要使用 Fluent NHibernate Fluently.Configure API 來配置我們的應用程序。你可以看到更多的例子在Fluent configuration wiki 頁面中。
(https://github.com/jagregory/fluent-nhibernate/wiki/Fluent-configuration)
這就是並不完全正確,但我們正在創建 SessionFactory,但我們還沒有配置任何東西;所以讓我們來配置我們的數據庫。
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(
SQLiteConfiguration.Standard
.UsingFile("firstProject.db")
)
.BuildSessionFactory();
}
就這樣,我們指定了我們在使用基於文件的 SQLite 數據庫。你可以了解更多關於數據庫配置 wiki 頁面中的數據庫配置 API。
我們需要為 NHibernate提供我們已經創建的映射。要做到這一點,我們把我們的配置添加到Mappings的調用。
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(
SQLiteConfiguration.Standard
.UsingFile("firstProject.db")
)
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<Program>())
.BuildSessionFactory();
}
就是這樣;這是您的應用程序配置 !
你現在應該能夠運行該應用程序並查看您查詢輸出到控制台窗口的結果 (假設實際上創建了 SQLite 數據庫架構; 否則,見下面的架構生成部分)。
Bargin Basin
Products:
Potatoes
Fish
Milk
Bread
Cheese
Staff:
Daisy Harrison
Jack Torrance
Sue Walkters
SuperMart
Products:
Bread
Cheese
Waffles
Staff:
Bill Taft
Joan Pope
你在那邊;這是你第一次的Fluent NHibernate 項目創建和運行 !
生成數據庫架構
如果你沒有手動創建此應用程序的數據庫架構,那么你第一次運行它將失敗。你可以做的一些事情,但它需要直接針對 NHibernate Configuration 對象;我們可以使用 ExposeConfiguration 方法做到。這一調用結合的方法來生成數據庫架構,然后你就能夠在運行時創建您的數據庫架構。
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(
SQLiteConfiguration.Standard
.UsingFile("firstProject.db")
)
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<Program>())
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();
}
private static void BuildSchema(Configuration config)
{
// delete the existing db on each run
if (File.Exists(DbFile))
File.Delete(DbFile);
// this NHibernate tool takes a configuration (with mapping info in)
// and exports a database schema from it
new SchemaExport(config)
.Create(false, true);
}
你可以閱讀更多關於Fluent configuration,在wiki頁面中。
(https://github.com/jagregory/fluent-nhibernate/wiki/Fluent-configuration)
