英文原文地址: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)