NHibernate官网Demo
如果你正在阅读此文,我们假设你刚刚下载了NHibernate的,并希望开始使用它。
本教程将讨论如下几个步骤:
- 安装NHibernate
- 定义一个简单的业务对象类。
- 创建一个NHibernate的映射加载和保存业务对象。
- 配置NHibernate与本地数据库进行连接。
- 自动生成一个数据库。
- 使用Repository模式编写简单的CRUD代码。
- 使用单元测试,以确保代码工作正常。
这是我们期待的最终结果:
但首要的事情是:
让我们从您刚刚下载的ZIP文件开始。
安装NHibernate的
如果您已经下载了NHibernate的二进制压缩文件,你需要做的就是将该文件解压缩到合适的地方。我通常创建一个文件夹名为SharedLibs c:\Code\SharedLibs\NHibernate并把压缩文件
解压到那里。 But whatever you're comfortable with.但是,不管你舒服。 This is your SharedLib folder from which you need to add your references to the NHibernate and NUnit dlls.这是您的SharedLib从哪个文件夹,您需要添加你提述NHibernate和NUnit的DLL的。 Add references to NHibernate to both the demo project and the unit test project.添加引用NHibernate的既示范项目和单元测试项目。
就是这样!NHibernate安装好了(容易嗯)。 We'll talk you through using it with Visual Studio in a moment.稍后我们将通过Visual Studio使用它。 首先让我们看看我们如如何创建一个项目。 注意:此代码是依赖于Visual Studio 2008和Net框架3.5。
创建项目
Before we start building our application and business objects, we'll need to create a blank project to put them in. Fire up Visual Studio and create a new Class Library project.在我们开始建立我们的应用程序和业务对象前,我们需要创建一个空白项目,开启Visual Studio并创建一个新的类库项目。 现在让我们看看一个有趣东西:创建业务对象。
定义业务对象
从定义一个非常简单的领域对象的开始。 For the moment it consists of one entity called Product .对于目前它的一个实体组成所谓的产品 。该产品有3个属性:名称,类别和停产。
添加一个Domain文件夹到您的解决方案FirstSample项目中。 添加一个新类Product.cs到此文件夹。代码是非常简单,使用自动属性(一新的C#3.0编译器的功能)
1: namespace FirstSolution.Domain
2: {
3: public class Product
4: {
5: public string Name { get; set; }
6: public string Category { get; set; }
7: public bool Discontinued { get; set; }
8: }
9: }
现在,我们希望能够持久化实体到(关系型)数据库。我们选择了NHibernate来担任这项任务。 一个领域实例对象对应于数据库表中的行。 因此,我们要定义实体和数据库中相应表之间的映射。这个映射既可以通过定义映射文件(XML的文件)或装饰实体属性。 我们从映射文件开始。
定义映射
在FirstSample中创建一个存放映射文件的文件夹。添加一个新的XML文档到该文件夹,命名为“Product .hbm.xml ”。请注意文件名中“HBM的”部分。 这是NHibernate用于自动识别为映射文件的约定。 定义为“嵌入的资源”编译该XML文件”。
在Windows资源管理器找到NHibernate的src文件夹下的nhibernate-mapping.xsd ,并将其复制到您的SharedLibs文件夹。 我们现在可以使用此XML架构定义确定我们的映射文件。 当编辑一个XML映射文件时, VS会提供智能提示和验证。
回到VS给Product.hbm.xml文件添加架构文档。
让我们现在就开始。 每个映射文件必须定义一个<hibernate-mapping>根节点
1: <?xml version="1.0" encoding="utf-8" ?>
2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3: assembly="FirstSolution" namespace="FirstSolution.Domain">
4: <!-- more mapping info here -->
5: </hibernate-mapping>
映射文件中引用一个领域类时,你总是必须提供类(如FirstSample.Domain.Product,FirstSample)完全限定名。 To make the xml less verbose you can define the assembly name (in which the domain classes are implemented and the namespace of the domain classes in the two attributes assembly and namespace of the root node. It's similar to the using statement in C#.为了使XML的更加简洁,您可以定义程序集名称(在该域类实施和域的类的两个属性装配和根节点的命名空间。它类似C#的using语句。
现在,我们必须首先确定该产品的实体的主键 。从技术上讲,我们可以采取的产品属性“Name”,因为这个属性已定义,并且是唯一的。但是通常使用一个代理键代替。 因此,我们在实体里添加一个属性叫做“Id”。我们使用 GUID作为ID类型,但它也可以是一个int或Long型。
1: using System;
2: namespace FirstSolution.Domain
3: {
4: public class Product
5: {
6: public Guid Id { get; set; }
7: public string Name { get; set; }
8: public string Category { get; set; }
9: public bool Discontinued { get; set; }
10: }
11: }
完整的映射文件
1: <?xml version="1.0" encoding="utf-8" ?>
2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3: assembly="FirstSolution" namespace="FirstSolution.Domain">
4: <class name="Product">
5: <id name="Id">
6: <generator class="guid" />
7: </id>
8: <property name="Name" />
9: <property name="Category" />
10: <property name="Discontinued" />
11: </class>
12: </hibernate-mapping>
NHibernate的没有得到我们的方式,例如,它定义了许多合理的默认值。 So if you don't provide a column name for a property explicitly it will name the column according to the property.所以,如果你不提供一个属性明确的列名,将名字列按财产。 Or NHibernate can automatically infer the name of the table or the type of the column from the class definition.或者NHibernate的可以自动推断出表或从类的定义列类型的名称。 As a consequence my xml mapping file is not cluttered with redundant information.因此我的XML映射文件不堆满了多余的信息。 请参阅联机文档映射更详细的解释。你可以找到它在这里 。
您的解决方案资源管理器应该像现在这样(Domain.cd包含我们简单的域类图)。您将有增加的设计文件夹,创建自己的类图,这是个很好的做法,而不是为这个excercise的目的要求。
配置NHibernate
我们现在必须告诉NHibernate我们想用什么数据库产品,并提供一个连接字符串的形式连接的详细信息。 NHibernate的支持许多数据库产品!
添加一个新的XML文件到FirstSolution项目并并命名为“hibernate.cfg.xml”。 设置其属性“ 复制到输出 ”为“ 始终复制 ”。 Since we are using SQL Server Compact Edition in this first sample enter the following information into the xml file 由于我们在这个示例中使用的是SQL Server Compact Edition,在XML文件中输入以下信息:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
3: <session-factory>
4: <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider
</property>
5: <property name="dialect">NHibernate.Dialect.MsSqlCeDialect</property>
6: <property name="connection.driver_class">NHibernate.Driver.SqlServerCeDriver</property>
7: <property name="connection.connection_string">Data Source=FirstSample.sdf</property>
8: <property name="show_sql">true</property>
9: </session-factory>
10: </hibernate-configuration>
有了这个配置文件,我们告诉NHibernate我们的目标数据库是MS SQL Server的精简版,而数据库的名称应FirstSample.sdf(=连接字符串)。 我们还确定,我们希望看到NHibernate生成的SQL 并发送到数据库(强烈推荐用于调试过程中)。仔细检查你的代码确认没有错字!
添加一个空数据库命名FirstSample.sdf到FirstSample项目(选择本地数据库作为模板)
单击添加,忽略DataSet创建向导(只需点击取消)。
测试安装
现在来检验我们的设置。 首先确认下SharedLibs文件夹包含下列文件
最后8文件,您可以在“Microsoft SQL Server Compact Edition"程序文件夹Programs folder目录中找到。
注:system.data.sqlserverce.dll 是在子文件夹Desktop中。
所有其他文件中可以NHibernate文件夹找到
在测试项目中添加对FirstSample项目的引用。此外添加NHibernate.dll,nunit.framework.dll和Systm.Data.SqlServerCe.dll引用(记得在SharedLibs文件夹中!)。 注意设置system.data.sqlserverce.dll程序集属性“ 复制本地 ”为“true”,默认情况下它被设置为false! Add a copy of hibernate.cfg.xml to the root of this unit test project. 添加hibernate.cfg.xml副本到单元测试项目的根目录下。 Direct action with NHibernate in the NUnit project needs access to this file.与NHibernate的直接行动,在NUnit的项目需要访问这个文件。
添加一类叫做GenerateSchema_Fixture到您的测试项目。 你的测试项目,现在应该是这样的
我们还需要7个sqce*.dll 文件在输出目录。 We can do this by using a post-build event in VS.我们可以在VS中通过使用生成后事件。 在“生成后事件命令行”中输入如下命令:
copy $(ProjectDir)..\..\SharedLibs\sqlce*.dll $(ProjectDir)$(OutDir)
现在在GenerateSchema_Fixture文件中添加以下代码在:
1: using FirstSolution.Domain;
2: using NHibernate.Cfg;
3: using NHibernate.Tool.hbm2ddl;
4: using NUnit.Framework;
5: namespace FirstSolution.Tests
6: {
7: [TestFixture]
8: public class GenerateSchema_Fixture
9: {
10: [Test]
11: public void Can_generate_schema()
12: {
13: var cfg = new Configuration();
14: cfg.Configure();
15: cfg.AddAssembly(typeof (Product).Assembly);
16: new SchemaExport(cfg).Execute(false, true, false, false);
17: }
18: }
19: }
该测试方法的第一行创建一个NHibernate的配置类的新实例。这个类是用来配置NHibernate的。在第二行,我们告诉NHibernate配置本身。因为我们在测试方法里没有提供任何配置信息,NHibernate会自动寻找出配置信息。因此,NHibernate将在输出目录中搜索一个文件名为hibernate.cfg.xml的配置文件。这正是我们想要的,因为我们有这个文件中定义了设置。
第三行代码告诉NHibernate在含有Products类的程序集中可以找到映射信息。在目前来说,只能找到一个作为嵌入资源的文件(Product.hbm.xml)。
第四行代码使用了NHibernate的SchemaExport辅助类,“魔术”般地自动在数据库中生成了表。SchemaExport会在数据库中的产生一张product表,每次调用它会删除表和表中数据并重新创建它。
注:此测试方法,我们并不是查看NHibernate是否正确履行自己的工作(你可以肯定它),而是确定是否正确设置了我们的系统。 但是,您可以检查数据库,看到了新创建的'product'表。
If you have TestDriven.Net installed you can now just right click inside the test method and choose " Run Test(s) " to execute the test.如果您安装TestDriven.Net,您现在可以只右击里面的试验方法,并选择“ 运行测试(秒)”执行测试。
如果一切都ok的话,请查看输出窗口里的输出结果
如果您安装ReSharper,您可以开始按一下黄色左侧边界绿色圆圈,然后选择运行测试。
结果如下
可能存在的问题
如果测试失败,请仔细检查您的目标目录中的下列文件(m:dev\projects\FirstSolution\src\FirstSolution.Tests\bin\debug)
还要仔细检查NHibernate的配置文件中没有错别字(hibernate.cfg.xml中)或在映射文件(Product.hbm.xml)。 最后,检查是否设置了“ 建设行动 ”的映射文件(Product.hbm.xml)为“ 嵌入的资源 ”。 如果测试成功那么继续以下操作。
我们的第一个CRUD操作
现在很明显我们的系统已准备好开始。我们成功地落实我们的域名,明确了映射文件和配置NHibernate。最后,我们用NHibernate从领域对象自动生成名数据库模式(和我们的映射文件)。
按照领域驱动设计(见例如,由Eric埃文斯领域驱动设计 ),我们定义一个仓储包含所有CRUD操作库(创建,读取,更新和删除)。仓储是域模型的一部分并不包含实现 !实现是基础设施。 我们要保持我们的领域对象是持久化无知(PI)的。
在FirstSolution项目的“domain”文件夹下新建一个接口,命名为IProductRepository。 让我们定义以下接口
1: using System;
2: using System.Collections.Generic;
3: namespace FirstSolution.Domain
4: {
5: public interface IProductRepository
6: {
7: void Add(Product product);
8: void Update(Product product);
9: void Remove(Product product);
10: Product GetById(Guid productId);
11: Product GetByName(string name);
12: ICollection<Product> GetByCategory(string category);
13: }
14: }
1: [TestFixture]
2: public class ProductRepository_Fixture
3: {
4: private ISessionFactory _sessionFactory;
5: private Configuration _configuration;
6:
7: [TestFixtureSetUp]
8: public void TestFixtureSetUp()
9: {
10: _configuration = new Configuration();
11: _configuration.Configure();
12: _configuration.AddAssembly(typeof (Product).Assembly);
13: _sessionFactory = _configuration.BuildSessionFactory();
14: }
15: }
在该方法的第四行TestFixtureSetUp我们创建了一个会话工厂。这是一个昂贵的过程,因此应只执行一次。 这就是为什么我把它放在整个测试周期只执行一次的方法里。
To keep our test methods side effect free we re-create our database schema before the execution of each test method.为了保证我们的测试方法没有副作用,在测试每个方法前我们重新建立数据库,因此,我们添加如下方法:
1: [SetUp]
2: public void SetupContext()
3: {
4: new SchemaExport(_configuration).Execute(false, true, false, false);
5: }
现在我们可以实现测试并添加一个新的产品实例数据库。 Start by adding a new folder called Repositories to your FirstSolution project.在FirstSolution项目中添加一个新文件夹命名为“Repositories ”。在此文件夹中添加一个类ProductRepository。 使ProductRepository继承接口IProductRepository。
1: using System;
2: using System.Collections.Generic;
3: using FirstSolution.Domain;
4: namespace FirstSolution.Repositories
5: {
6: public class ProductRepository : IProductRepository
7: {
8: public void Add(Product product)
9: {
10: throw new NotImplementedException();
11: }
12: public void Update(Product product)
13: {
14: throw new NotImplementedException();
15: }
16: public void Remove(Product product)
17: {
18: throw new NotImplementedException();
19: }
20: public Product GetById(Guid productId)
21: {
22: throw new NotImplementedException();
23: }
24: public Product GetByName(string name)
25: {
26: throw new NotImplementedException();
27: }
28: public ICollection<Product> GetByCategory(string category)
29: {
30: throw new NotImplementedException();
31: }
32: }
33: }
处理数据
现在回到ProductRepository_Fixture测试类和实施第一个测试方法
1: [Test]
2: public void Can_add_new_product()
3: {
4: var product = new Product {Name = "Apple", Category = "Fruits"};
5: IProductRepository repository = new ProductRepository();
6: repository.Add(product);
7: }
该测试方法第一次运行会失败,因为我们还没有在类repository添加实现方法。马上做, 别急 ,我们要定义一个小帮助类是为我们提供了需要的会话对象。
1: using FirstSolution.Domain;
2: using NHibernate;
3: using NHibernate.Cfg;
4: namespace FirstSolution.Repositories
5: {
6: public class NHibernateHelper
7: {
8: private static ISessionFactory _sessionFactory;
9: private static ISessionFactory SessionFactory
10: {
11: get{
12: if(_sessionFactory == null)
13: {
14: var configuration = new Configuration();
15: configuration.Configure();
16: configuration.AddAssembly(typeof(Product).Assembly);
17: _sessionFactory = configuration.BuildSessionFactory();
18: }
19: return _sessionFactory;
20: }
21: }
22: public static ISession OpenSession()
23: {
24: return SessionFactory.OpenSession();
25: }
26: }
27: }
这个类只有在客户第一次需要一个新的会话时创建一个会话工厂。
现在,我们可以按下面在ProductRepository定义Add方法:
1: public void Add(Product product)
2: {
3: using (ISession session = NHibernateHelper.OpenSession())
4: using (ITransaction transaction = session.BeginTransaction()
5: {
6: session.Save(product);
7: transaction.Commit();
8: }
9: }
该测试方法第二次运行将再次失败,出现以下消息
这是因为NHibernate的默认配置为使用的所有实体延迟加载的。那是推荐的方法,我衷心建议不要更改最大限度的灵活性了。
我们怎样才能解决这个问题?这个问题很容易解决,只要给领域对象所有的属性(和方法)定义为virtual即可。让我们来修改下“Product”类:
1: public class Product
2: {
3: public virtual Guid Id { get; set; }
4: public virtual string Name { get; set; }
5: public virtual string Category { get; set; }
6: public virtual bool Discontinued { get; set; }
7: }
现在,再次运行测试。它应该成功,我们得到以下输出
注意NHibernate后面的SQL。
现在,我们认为,我们已经成功地把一个新的产品插入到数据库中。但是,我们来测试它是否确实是这样。 继续我们的测试方法
1: [Test]
2: public void Can_add_new_product()
3: {
4: var product = new Product {Name = "Apple", Category = "Fruits"};
5: IProductRepository repository = new ProductRepository();
6: repository.Add(product);
7: // use session to try to load the product
8: using(ISession session = _sessionFactory.OpenSession())
9: {
10: var fromDb = session.Get<Product>(product.Id);
11: // Test that the product was successfully inserted
12: Assert.IsNotNull(fromDb);
13: Assert.AreNotSame(product, fromDb);
14: Assert.AreEqual(product.Name, fromDb.Name);
15: Assert.AreEqual(product.Category, fromDb.Category);
16: }
17: }
再次运行测试。 希望它会成功...
现在,我们准备实现repository其他方法。为了测试这一点,我们也希望有一个仓库(即数据库表)已经包含了一些产品。 没有什么比这更容易。只需添加一个方法CreateInitialData的测试类如下
1: private readonly Product[] _products = new[]
2: {
3: new Product {Name = "Melon", Category = "Fruits"},
4: new Product {Name = "Pear", Category = "Fruits"},
5: new Product {Name = "Milk", Category = "Beverages"},
6: new Product {Name = "Coca Cola", Category = "Beverages"},
7: new Product {Name = "Pepsi Cola", Category = "Beverages"},
8: };
9: private void CreateInitialData()
10: {
11: using(ISession session = _sessionFactory.OpenSession())
12: using(ITransaction transaction = session.BeginTransaction())
13: {
14: foreach (var product in _products)
15: session.Save(product);
16: transaction.Commit();
17: }
18: }
从SetupContext方法中调用此方法(创建模式后,调用)。 现在每个数据库模式后,创建数据库时填充一些产品。
让我们用下面的代码测试Update 方法
1: [Test]
2: public void Can_update_existing_product()
3: {
4: var product = _products[0];
5: product.Name = "Yellow Pear";
6: IProductRepository repository = new ProductRepository();
7: repository.Update(product);
8: // use session to try to load the product
9: using (ISession session = _sessionFactory.OpenSession())
10: {
11: var fromDb = session.Get<Product>(product.Id);
12: Assert.AreEqual(product.Name, fromDb.Name);
13: }
14: }
当首次运行这段测试代码时将失败,因为repository类中尚未实现该更新方法。 注 :这是因为拓展署的首次运行测试,应该始终无法预期的行为!
类似于Add方法,我们贯彻落实repository更新方法。唯一的区别是,我们调用是NHibernate会话对象的update方法。
1: public void Update(Product product)
2: {
3: using (ISession session = NHibernateHelper.OpenSession())
4: using (ITransaction transaction = session.BeginTransaction())
5: {
6: session.Update(product);
7: transaction.Commit();
8: }
9: }
再次运行测试,可以看到它测试通过。
删除方法是最直截了当的。当测试记录是否真的被删除,我们只要断言会话对象get方法返回的值等于空。这里是测试方法。
1: [Test]
2: public void Can_remove_existing_product()
3: {
4: var product = _products[0];
5: IProductRepository repository = new ProductRepository();
6: repository.Remove(product);
7: using (ISession session = _sessionFactory.OpenSession())
8: {
9: var fromDb = session.Get<Product>(product.Id);
10: Assert.IsNull(fromDb);
11: }
12: }
这里是删除方法在repository的实现
1: public void Remove(Product product)
2: {
3: using (ISession session = NHibernateHelper.OpenSession())
4: using (ITransaction transaction = session.BeginTransaction())
5: {
6: session.Delete(product);
7: transaction.Commit();
8: }
9: }
查询数据库
我们仍然必须执行三个查询数据库对象方法。让我们先从最容易的GetById。首先,我们编写测试
1: [Test]
2: public void Can_get_existing_product_by_id()
3: {
4: IProductRepository repository = new ProductRepository();
5: var fromDb = repository.GetById(_products[1].Id);
6: Assert.IsNotNull(fromDb);
7: Assert.AreNotSame(_products[1], fromDb);
8: Assert.AreEqual(_products[1].Name, fromDb.Name);
9: }
以下是实现代码来完成测试
1: public Product GetById(Guid productId)
2: {
3: using (ISession session = NHibernateHelper.OpenSession())
4: return session.Get<Product>(productId);
5: }
现在,这很简单。对于下面的两个方法,我们使用的会话对象的新方法。让我们先从getByName方法开始。像往常一样,我们先写测试
1: [Test]
2: public void Can_get_existing_product_by_name()
3: {
4: IProductRepository repository = new ProductRepository();
5: var fromDb = repository.GetByName(_products[1].Name);
6: Assert.IsNotNull(fromDb);
7: Assert.AreNotSame(_products[1], fromDb);
8: Assert.AreEqual(_products[1].Id, fromDb.Id);
9: }
该getByName方法的实现,可以通过两种不同的方法。第一个是用HQL(Hibernate查询语言)和第二个HCQ(Hibernate 条件查询)。 让我们先从HQL开始。 HQL是面向查询语言,类似(但不等于)的SQL的对象。
To be added: implemetation of GetByName using HQL.要添加:implemetation的GetByName用HQL。 Implement HCQ as below this works as expected and returns a product entity.落实低于这个工程预期HCQ并返回一个产品的实体。
In the above sample I have introduced a commonly used technique when using NHibernate.在上面的示例我已经介绍了常用的技术,在使用NHibernate的。它被称为流利的接口 。 因此,该代码更加简洁,并更容易理解。 You can see that a HQL query is a string which can have embedded (named) parameters.你可以看到,HQL查询是一个可以在嵌入式()的名字命名的参数字符串。 参数前缀':'。 NHibernate defines many helper methods (like SetString used in the example) to assign values of various types to those parameters. NHibernate的定义了许多辅助方法(如本例中使用SetString)分配不同类型的值为这些参数。 Finally by using UniqueResult I tell NHibernate that I expect only one record to return.通过使用UniqueResult我告诉NHibernate的,我希望只有一个记录,最后返回。 If more than one record is returned by the HQL query then an exception is raised.如果有一个以上的纪录是由HQL查询,然后返回一个异常引发。 To get more information about HQL please read the online documentation .要获得更多有关的HQL信息,请阅读联机文档 。
第二个版本使用条件查询来搜索要求的产品。 You need to add a reference to NHibernate.Criterion on your repository page.您需要添加在您的库页提及NHibernate.Criterion。
1: public Product GetByName(string name)
2: {
3: using (ISession session = NHibernateHelper.OpenSession())
4: {
5: Product product = session.CreateCriteria(typeof(Product))
6: .Add(Restrictions.Eq("Name", name))
7: .UniqueResult<Product>();
8: return product;
9: }
10: }
对NHibernate的许多用户认为,这种方法更面向对象的。另一方面,一个复杂的criteria 查询语法可能很快就会变得很难理解。
最后一个是方法GetByCategory实现。 此方法返回的产品清单。 测试代码实现如下
1: [Test]
2: public void Can_get_existing_products_by_category()
3: {
4: IProductRepository repository = new ProductRepository();
5: var fromDb = repository.GetByCategory("Fruits");
6: Assert.AreEqual(2, fromDb.Count);
7: Assert.IsTrue(IsInCollection(_products[0], fromDb));
8: Assert.IsTrue(IsInCollection(_products[1], fromDb));
9: }
10: private bool IsInCollection(Product product, ICollection<Product> fromDb)
11: { foreach (var item in fromDb)
12: if (product.Id == item.Id)
13: return true;
14: return false;
15: }
该方法本身可能包含以下代码
1: public ICollection<Product> GetByCategory(string category)
2: {
3: using (ISession session = NHibernateHelper.OpenSession())
4: {
5: var products = session .CreateCriteria(typeof(Product))
6: .Add(Restrictions.Eq("Category", category))
7: .List<Product>();
8: return products;
9: }
10: }
摘要
在此,我向您展示如何实现一个基本示例域,定义映射到数据库,以及如何配置NHibernate持久化对象到数据库中。我已经向您展示了如何编写和测试领域域对象的CRUD方法。我使用微软的SQL精简版作为示例数据库,但任何其他支持的数据库都可以(你只需要改变相应的hibernate.cfg.xml文件)。 Ee have no dependencies on external frameworks or tools other than the database and NHibernate itself (.NET of course never counts here).夷对外部框架或工具比其他数据库和NHibernate的本身不依赖(。当然不会,关键网)。
官网原文:http://nhforge.org/wikis/howtonh/your-first-nhibernate-based-application.aspx