该文为转载内容
NHibernate 入门指南
标签: databasesession数据库测试domainclass
2009-12-18 19:22 3699人阅读 评论(3) 收藏 举报
分类:
DotNet(5)
Welcome to NHibernate - 欢迎使用NHibernate
If you‘re reading this, we assume that you‘ve just downloaded NHibernate and want to get started using it.
如果你正阅读本文,我们想您已经 下载NHibernate 并且想要开始使用它了。
This tutorial will talk you through the following:
- Installing NHibernate
- Defining a simple business object class.
- Create an NHibernate mapping to load and save the business object.
- Configure NHibernate to talk to your local database.
- Automatically generate a database
- Writing simple CRUD code using the Repository pattern.
- Using Unit Tests to make sure the code is working correctly.
本指南为你讲述如下内容:
- 安装 NHibernate
- 定义一个简单的业务对像类
- 创建一个 NHibernate 映射用于载入和保存业务对像
- 配置 NHibernate 用于和本地数据库对话
- 自动生成数据库
- 使用数据栈(仓储)模式写简单的 CRUD (Create 增, read 查, update 改 and delete 删)代码
- 使用单元测试确保代码正确执行
This is what we‘re aiming for:
这是我们的任务:
But first things first [:)]
Lets start by actually doing something with that ZIP file you just downloaded.
首先[:)]
让我们先对你刚下载的ZIP文件做点功课。
Installing NHibernate - 安装 NHibernate
If you‘ve downloaded the NHibernate binaries in a zip file, all you need to do is extract that file to somewhere sensible. I usually create a folder called SharedLibs c:/Code/SharedLibs/NHibernate and extract the zip to there. 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. Add references to NHibernate to both the demo project and the unit test project.
如果你下载了 NHibernate 二进制文件的压缩包,你所要做的是解压文件到合理的地方。我通常会创建一个 SharedLibs 文件夹,然后解压文件到 c:/Code/SharedLibs/NHibernate 下。只要你觉得方便就行。SharedLibs 文件夹是用来存放 NHibernate 和 NUnit.dlls 引用的。添加 NHibernate 引用到示例项目和测试项目。
That‘s it! NHibernate is installed (easy huh). We‘ll talk you through using it with Visual Studio in a moment. First let‘s look at how we go about creating a project. Note this code is dependent on Visual Studio 2008 and .Net Framework 3.5.
好了!NHibernate 安装完成了(容易吧)。我们先花一小会儿时间,从在Visual Studio 中使用它说起。首先让我们先来看一下怎样创建一个项目。注意此代码基于 Visual Studio 2008 和 .Net Framework 3.5.
Create Your Project - 创建你的项目
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. Let‘s now look at something interesting: creating a business object.
在我们开始构建我们的应用和业务对像前,我们需要创建一个空项目然后再把它们添加其中。启动 Visual Studio 然后创建一个新的类库项目。现在让我看点有趣的事情:创建一个业务对像。
Defining the Business Objects - 定义一个业务对像
Lets start by defining a very simple domain. For the moment it consists of one entity called Product. The product has 3 properties Name, Category and Discontinued.
我们就从定义简单的领域开始。稍后它将包含一个名为 Product(产品)的实体。prodct 有 Name (名称), Category (分类)和 Discontinued (停用)3个属性。
Add a folder Domain to the FirstSample project of your solution. Add a new class Product.cs to this folder. The code is very simple and uses automatic properties (a feature of the new C# 3.0 compiler)
添加一个 Domain 文件夹到你的解决方案中的 FirstSample 项目。添加一个新类 Product.cs 此文件夹。这段代码非常简单而且使用的自动属性(C# 3.0 编译器的新特性)
namespace FirstSolution.Domain
{
public class Product
{
public string Name { get; set; }
public string Category { get; set; }
public bool Discontinued { get; set; }
}
}
Now we want to be able to persist instances of this entity in a (relational) database. We have chosen NHibernatefor this task. An instance of an entity in the domain corresponds to a row in a table
in the database. So we have to define a mapping between the entity and
the corresponding table in the database. This mapping can be done either
by defining a mapping file (an xml-document) or by decorating the
entity with attributes. I‘ll start with the mapping file.
现在我们想能够持久化这个在(关系型)数据库中的实体。我们选择 NHibernate 来完成此项任务。领域中实体的一个实例对应于数据库中表的一条记录。因此我们必须在实体与相应的数据库中的表之间建立一个映射。这个映射可以通过定义一个映射文件(一个xml-文件)或者通过特性来修饰实体类。我将采用映射文件。
Define the Mapping - 定义一个映射
Create a folder Mappings in the FirstSample project. Add a new xml-document to this folder and call it Product.hbm.xml.
Please note the "hbm" part of the file name. This is a convention used
by NHibernate to automatically recognize the file as a mapping file.
Define "Embedded Resource" as Build Action for this xml file.
在 FirstSample 项目中创建一个 Mappings 文件夹。添加一个新的 xml 文件到此文件夹命名为 Product.hbm.xml 。请注意 “hbm” 是文件名的一部分。这是 NHibernate 自动识别映射文件的规定。将此 xml 文件的属性 生成操作 设为“嵌入的资源”。
In the Windows Explorer locate the
nhibernate-mapping.xsd in the src folder of NHibernate and copy it to
your SharedLibs folder. We can now use this xml schema definition file
when defining our mapping files. VS will then provide intellisense and
validation when editing an xml mapping document.
在 Windows 资源管理器中找到位于 NHibernate 的 src 文件夹下的 nhibernate-mapping.xsd 文件然后复制到 SharedLibs 文件夹中。现在我们可用此 xml schema (文档结构描述文件)去规范我们的映射文件。这样VS 就可经在我们编辑 xml 映射文件时,为我们提供智能感知和验证功能。
Back in VS add the schema to the Product.hbm.xml file
回到 VS 为 Product.hbm.xml 文件添加文档结构描述文件
Let‘s start now. Each mapping file has to define a root node
让我现在开始。每个映射文件必须定义一个 根结点
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="FirstSolution"
namespace="FirstSolution.Domain">
<!-- more mapping info here -->
</hibernate-mapping>
In a mapping file when referencing a
domain class you always have to provide the fully qualified name of the
class (e.g. 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#.
当在映射文件中引用领域中的类时你需要提供类的完全限定名(例如:FirstSample.Domain.Product, FirstSample)。为了让 xml 文件不显得冗长你可以指定程序集名称(你可以在根结点添加 assembly 与 namespace 属性为类指定程序集和命名空间)。这很像 C# 中的 using 语句。
Now we have to first define a primary key for the product entity. Technically we could take the property Name
of the product since this property must be defined and has to be
unique. But it is common to use a surrogate key instead. For thus we add
a property to our entity and call it Id. We use Guid as the type of the Id but it can as well be an int or a long.
现在我们必须为 product(产品)实体定义主键。既然产品名称必须唯一,技术上讲我们可以使用产品的 Name (名称)作主键。但通常我们会使用代理主键。因此我们给实体增加一个 Id 属性。我们使用 Guid (全局唯一标识符)作为 Id 的类型,当然 Id 也可以是 int 或 long 。
using System;
namespace FirstSolution.Domain
{
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public bool Discontinued { get; set; }
}
}
The complete mapping file
映射文件的全部内容
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="FirstSolution"
namespace="FirstSolution.Domain">
<class name="Product">
<id name="Id">
<generator class="guid" />
</id>
<property name="Name" />
<property name="Category" />
<property name="Discontinued" />
</class>
</hibernate-mapping>
NHibernate doesn‘t get in our way such
as that it defines many reasonable defaults. 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. As a
consequence my xml mapping file is not cluttered with redundant
information. Please refer to the online documentation for more detailed
explanation of the mapping. You can find it here.
NHibernate 很简便,它定义的许多合理的默认值,所以如果你没有显式的为属性提供列名,它就会根据属性名定义列名。而且
NHibernate 可以通过类的定义自动推断表名或列类型。所以我的 xml
映射文件不会参杂冗余信息。有关映射的更为详尽的说明,请参考在线文档。参阅此处。
Your solution explorer should look like this now (Domain.cd
contains the class diagram of our simple domain). You will have added
the design folder and created the class diagram yourself although this
is for good practice and not required for the purposes of this
excercise.
你的解决方案资源管理器现在可能看起来像这样(Domain.cd 包含我们简单的领域中的类关系图)。你可以添加 design 文件夹,然后自己创建类关系图。虽然这是个好举措,但在本练习中还不需要。
Configure NHibernate - 配置 NHibernate
We now have to tell NHibernate which
database product we want to use and provide it the connection details in
form of a connection string. NHibernate supports many many database
products!
我们现在必须告诉 NHibernate 我们将使用哪一个数据库产品,还要以连接字串的形式提供有关连接的详细信息。NHibernate 支持许多种数据库产品!
Add a new xml file to the FirstSolution project and call it hibernate.cfg.xml. Set its property "Copy to Output" to "Copy always". Since we are using SQL Server Compact Edition in this first sample enter the following information into the xml file
添加一个新的 xml 文件到 FirstSolution 项目,并命名为 hibernate.cfg.xml 。设置其属性“复制到输出目录”为“始终复制”。在本例中,由于我们使用的是 SQL Server Compact Edition,所以在 xml 文件中输入如下信息。
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="dialect">NHibernate.Dialect.MsSqlCeDialect</property>
<property name="connection.driver_class">NHibernate.Driver.SqlServerCeDriver</property>
<property name="connection.connection_string">Data Source=FirstSample.sdf</property>
<property name="show_sql">true</property>
</session-factory>
</hibernate-configuration>
With this configuration file we tell
NHibernate that we want to use MS SQL Server Compact Edition as our
target database and that the name of the database shall be
FirstSample.sdf (=connection string). We have also defined that we want
to see the SQL NHibernate generates and sends to the
database (highly recommended for debugging purposes during development).
Double check that you have no typos in the code!
通过此配置文件我们就可以告诉 NHibernate 我们想用 MS SQL Server Compact Edition
作为我们的数据库对像,还有数据库名将会是 FirstSample.sdf(=连接字窜)。当然我们还定义了让我们看到 NHibernate
生成并发送到数据库的 SQL 语句(在改发过程中为了便于调试,强烈推荐此项设置)。仔细检查确保上述代码无误!
Add an empty database called FirstSample.sdf to the FirstSample project (choose Local Database as template)
添加一个空数据库并命名为 FirstSample.sdf 到 FistSample 项目(选择 本地数据库 作为模板)
Click Add and ignore the DataSet creation wizard (just hit Cancel).
单击添加,接下来跳过用数据源配置向导来创建数据集(单击取消)。
Test the Setup - 测试安装
It‘s now time to test our setup. First verify that you have the following files in your SharedLibs folder
现在就让我们测试一下安装。首先你要确定如下文件在 SharedLibs 文本夹
The last 8 files you can find in the "Microsoft SQL Server Compact Edition" directory in your Programs folder.
后面的8个文件你可以在你的程序文件夹(C:/Program Files)的“Microsoft SQL Server Compact Edition” 里找到。
Note: the System.Data.SqlServerCe.dll is located in the sub-folder Desktop.
注意:System.Data.SqlServerCe.dll 文件位于子文件夹 Desktop 下。
All other files can be found in the NHibernate folder
其它文件可以在 NHibernate 文件夹下找到
Add a reference to the FirstSample
project in your test project. Additionally add references to
NHibernate.dll, nunit.framework.dll and Systm.Data.SqlServerCe.dll
(remember to reference the files located in the SharedLibs folder!). Pay
attention to set the property "Copy Local" to true
for the assembly System.Data.SqlServerCe.dll since by default it is set
to false! Add a copy of hibernate.cfg.xml to the root of this unit test
project. Direct action with NHibernate in the NUnit project needs
access to this file.
添加对 FirstSample 项目的引用到你的测试项目。此外还要添加对 NHibernate.dll
,nunit.framework.dll 和 Systm.Data.SqlServerCe.dll 的引用(记住引用文件位于
SharedLibs 文件夹下!)。注意要将对所引用的程序集 System.Data.SqlServerCe.dll 的属性“复制本地”设为 true 默认情况下此项设置为 false! 复制 hibernate.cfg.xml 到测试项目的根结点下。NUnit 项目需要此文件来直接操作 NHinbernate 。
Add a class called GenerateSchema_Fixture to your test project. Your test project should now look like this
添加一个类命名为 GenerateSchema_Fixture 到你的测试项目。现在你的测试项目看起来像这样
We further need the 7 files sqce*.dll
in the output directory. We can do this by using a post-build event in
VS. Enter the following command in the "Post-build event command line"
我们另外需要7个文件 sqce*.dll 放置到输出目录。我们可以通过 VS 的生成后事件来完成此项工作。在生成后事件命令行文本框里输入如下命令。(此对话框可通过 右击项目 -> 选择【属性】 -> 选择【生成事件】 找到)
copy $(ProjectDir)../../SharedLibs/sqlce*.dll $(ProjectDir)$(OutDir)
$(ProjectDir) 表示 项目目录
$(OutDir) 表示 输出目录 bin/Debug
你需要根据你的项目存放位置来调整上述命令。如果路径中有空格应将路径有引号括起来。
例如:copy C:/CodeSharedLibs/sqlce*.dll "$(ProjectDir)$(OutDir)"
Now add the following code to the GenerateSchema_Fixture file
现在添加如下代码到 GenerateSchema_Fixture 文件
using FirstSolution.Domain;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using NUnit.Framework;
namespace FirstSolution.Tests
{
[TestFixture]
public class GenerateSchema_Fixture
{
[Test]
public void Can_generate_schema()
{
var cfg = new Configuration();
cfg.Configure();
cfg.AddAssembly(typeof (Product).Assembly);
new SchemaExport(cfg).Execute(false, true, false, false);
}
}
}
The first line of the test method
creates a new instance of the NHibernate configuration class. This class
is used to configure NHibernate. In the second line we tell NHibernate
to configure itself. NHibernate will look out for configuration
information since we do not provide any information here in the test
method. So NHibernate will search for a file called hibernate.cfg.xml in the output directory. That‘s exactly what we want since we have defined our settings in this file.
测试方法的第一行声明的 NHibernate configuration 类的一个实例。该类用来配置 NHibernate.
第二行代码通知 NHibernate 进行配置。NHibernate 会搜索配置信息是因为我们没有在测试方法里提供任何信息。因此
NHibernate 会搜索输出文件夹中 hibernate.cfg.xml 文件。这正是我们想要的因为我们早己对此文件进行了配置。
In the third line of the code we tell
NHibernate that it can find mapping information in the assembly which
contains also the class Product. At the time being it will only find one
such file (Product.hbm.xml) as an embedded resource.
第三行代码我们通知 NHibernate 在找到程序集(也就是包含 Product 类的程序集)中的映射信息。此时它会找到作为嵌入的资源的一个文件(Product.hbm.xml)。
The fourth line of code uses the
SchemaExport helper class of NHibernate to auto-"magically" generate the
schema in the database for us. SchemaExport will create the product
table in the database and each time you call it it will delete the table
and the table data and recreate it.
每四行代码使用 NHibernate 的 SchemaExport 助手类帮我们自动的有如“魔法”般的生成 数据库模 。SchemaExport 将会在数据库中创建 product (产品)表,而且你每次调用时它都会将表和表中的数据先行删除而后重建表。
Note: with this test method we do NOT
want to find out whether NHibernate does its job correctly (you can be
sure it does) but rater whether we have setup our system correctly.
However, you can check the database and see the newly created ‘product‘
table.
注意:通过这个测试方法我们 不 是想知道 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 你可以现在就可以在测试方法里右击然后选择“Run Test(s)” 来执行测试。
If every thing is ok you should see the following result in the output window
如果一切正常你会在输出窗口看到如下结果
If you have ReSharper installed you can just start the test by clicking the yellow-green circle on the left border and choose Run.
如果你己安装了 ReSharper 你可以通过单击左侧边框上的绿黄色圆圈然后选择 Run 来启动测试。
The result is as follows
结果如下所示
In case of Problems - 问题案例
If your test
fails double check that you find the following files in your target
directory (that is:
m:devprojectsFirstSolutionsrcFirstSolution.Testsbindebug)
如果你的测试失败仔细检查一下你会找到如下文件在你的目标目录中(就是:m:devprojectsFirstSolutionsrcFirstSolution.Testsbindebug)
Double check also if you have no typos
in the NHibernate configuration file (hibernate.cfg.xml) or in the
mapping file (Product.hbm.xml). Finally check whether you have set the "Build Action" of the mapping file (Product.hbm.xml) to "Embedded Resource". Only continue if the test succeeds.
还要仔细检查是否 NHibernate 配置文件(hibernate.cfg.xml)或是映射文件(Product.hbm.xml)无误。最后查一下是否已经设置映射文件(Product.hbm.xml)的“生成操作”为“嵌入的资源”。测试成功方可继续。
Our first CRUD operations - 我们的第一个 增查改删 操作。
Now obviously our system is ready to
start. We have successfully implemented our Domain, defined the mapping
files and configured NHibernate. Finally we have used NHibernate to
automatically generate the database schema from our Domain (and our
mapping files).
显然现在我们的系统就要准备运行了。我们已经成功的实现了我们的领域,定义了映射文件还有配置了 NHibernate 。最后我们使用 NHibernate 通过我们领域(还有映射文件)自动生成数据库模。
In the spirit of DDD (see e.g. Domain Driven Design
by Eric Evans) we define a repository for all crud operations (create,
read, update and delete). The repository interface is part of the domain
where as the implementation is not! The implementation is
infrastructure specific. We want to keep our domain persistence ignorant
(PI).
本着领域驱动设计思想(参看 领域驱动设计 Eric Evans 著)我们给全部的操作(增查改删)定义的数据栈。数据栈接口是领域部分而其实现则不然!数据栈的实现是底层细节。我们要保持我们领域的持久化无关(PI) 。
Add a new interface to the domain
folder of our FirstSolution project. Call it IProductRepository. Let‘s
define the following interface。
添加一个新的的接口到我们 FirstSolution 项目的 domain 文件夹。命名为 IProductRepository 。让我们定义如下接口。
using System;
using System.Collections.Generic;
namespace FirstSolution.Domain
{
public interface IProductRepository
{
void Add(Product product);
void Update(Product product);
void Remove(Product product);
Product GetById(Guid productId);
Product GetByName(string name);
ICollection GetByCategory(string category);
}
}
Add a class ProductRepository_Fixture to the test project of the solution and add the following code
添加一个类到解决方案的测试项目并添加如下代码
[TestFixture]
public class ProductRepository_Fixture
{
private ISessionFactory _sessionFactory;
private Configuration _configuration;
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
_configuration = new Configuration();
_configuration.Configure();
_configuration.AddAssembly(typeof (Product).Assembly);
_sessionFactory = _configuration.BuildSessionFactory();
}
}
In the fourth line of the method
TestFixtureSetUp we create a session factory. This is an expensive
process and should thus be executed only once. That‘s the reason why I
put it into this method which is only executed once during a test cycle.
TestFixtureSetUp 方法的第四行我们创建了一个 session (会话) 工厂。这一过程代价较高因此应当仅执行一次。这就是我为什么在这一测试周期会把它放到该法中的原因。
To keep our test methods side effect
free we re-create our database schema before the execution of each test
method. Thus we add the following method
为了让测试方法无副作用在我们每次执行测试方法前我们重新创建了我们数据库模。因此我们添加了如下方法。
[SetUp]
public void SetupContext()
{
new SchemaExport(_configuration).Execute(false, true, false, false);
}
And now we can implement the test
method to add a new product instance to the database. Start by adding a
new folder called Repositories to your FirstSolution project. Add a class ProductRepository to this folder. Make theProductRepository inherit from the interface IProductRepository.
现在我们可以实现测试方法,添加一个新的 product (产品)实例到数据库。首先添加一个新的 Repositories 文件夹到你 FirstSolution 的项目。添加一个 ProductRepository 类到此文件夹。确保 ProductRepository 继承自 IProductRepository 接口 。
using System;
using System.Collections.Generic;
using FirstSolution.Domain;
namespace FirstSolution.Repositories
{
public class ProductRepository : IProductRepository
{
public void Add(Product product)
{
throw new NotImplementedException();
}
public void Update(Product product)
{
throw new NotImplementedException();
}
public void Remove(Product product)
{
throw new NotImplementedException();
}
public Product GetById(Guid productId)
{
throw new NotImplementedException();
}
public Product GetByName(string name)
{
throw new NotImplementedException();
}
public ICollection<Product> GetByCategory(string category)
{
throw new NotImplementedException();
}
}
}
Manipulating Data - 操作数据
Now go back to the ProductRepository_Fixture test class and implement the first test method
现在回到 ProductRepository_Fixture 测试类实现第一个测试方法
Now go back to the ProductRepository_Fixture test class and implement the first test method
[Test]
public void Can_add_new_product()
{
var product = new Product {Name = "Apple", Category = "Fruits"};
IProductRepository repository = new ProductRepository();
repository.Add(product);
}
The first run of the test method will fail since we have not yet implemented the Add method in the repository class. Let‘s do it. But wait, we have to define a little helper class first which provides us session objects on demand.
初次运行测试方法会失败因为在栈类(数据栈类)中我们还没有实现 Add 方法。让我们来实现它。但是稍等,我们还得先定义一个小助手类它可以根据需要给我们提供 session 对像。
using FirstSolution.Domain;
using NHibernate;
using NHibernate.Cfg;
namespace FirstSolution.Repositories
{
public class NHibernateHelper
{
private static ISessionFactory _sessionFactory;
private static ISessionFactory SessionFactory
{
get
{
if(_sessionFactory == null)
{
var configuration = new Configuration();
configuration.Configure();
configuration.AddAssembly(typeof(Product).Assembly);
_sessionFactory = configuration.BuildSessionFactory();
}
return _sessionFactory;
}
}
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
}
}
This class creates a session factory only the first time a client needs a new session.
这个类仅在首次各户需要一个新的 session 时创建一个 session 工厂。
Now we can define the Add method in the ProductRepository as follows
现在我们可以定义这个 Add 方法到 ProductRepository 如下
public void Add(Product product)
{
using (ISession session = NHibernateHelper.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
session.Save(product);
transaction.Commit();
}
}
由于 NH2.1.0 版本里移除了对 Castle 项目的引用,所以如果你使用 NH2.1 版本需要在 hibernate.cfg.xml 文件中添加如下属性 详见此处
<property name="proxyfactory.factory_class">
NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu
</property>
此外还需要添加对程序集 NHibernate.ByteCode.LinFu.dll 的引用,此程序集位于 NHibernateRequired_For_LazyLoadingLinFu 文件夹下。
注意:此文件夹下还有一个 LinFu.DynamicProxy.dll 文件,NHibernate.ByteCode.LinFu.dll 会引用此文件。
The second run of the test method will again fail with the following message
再次运行测试方法会再次失败并带有如下信息
That‘s because NHibernate is by default configured to use lazy load for all entities. That is the recommended approach and I warmly recommend not to change it for a maximum of flexibility.
这是因为 NHibernate 默认配置全部实体使用 延迟加载 。这是推荐做法而且为了保证最大的灵活性强烈建议不要更改它。
How can we solve this issue? It‘s easy
we have to just make all our properties (and methods) of the domain
object(s) virtual. Let‘s do this for our Product class
我们怎样解决此问题呢?很简单我们必须使我们领域中对像的全部属性(和方法)为 virtual (虚) 。对我们的 Product 类我们可以这样做
public class Product
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual string Category { get; set; }
public virtual bool Discontinued { get; set; }
}
Now run the test again. It should succeed and we get the following output
现在再次运行测试。它应可以成功而且我们得到如下输出
Note the sql spit out by NHibernate.
注意通过 NHibernate 输出的 sql
Now we think that we have successfully
inserted a new product into the database. But let‘s test it whether it
is really so. Let‘s extend our test method
现在我们已经成功的插入一条新的产品记录到数据库。但是让我们来测试一下它是否真的这样做了。让我们扩展一下我们的测试方法
[Test]
public void Can_add_new_product()
{
var product = new Product {Name = "Apple", Category = "Fruits"};
IProductRepository repository = new ProductRepository();
repository.Add(product);
// use session to try to load the product
using(ISession session = _sessionFactory.OpenSession())
{
var fromDb = session.Get<Product>;(product.Id);
// Test that the product was successfully inserted
Assert.IsNotNull(fromDb);
Assert.AreNotSame(product, fromDb);
Assert.AreEqual(product.Name, fromDb.Name);
Assert.AreEqual(product.Category, fromDb.Category);
}
}
Run the test again. Hopefully it will succeed...
再次运行测试。希望它可以成功...
Now we are ready to implement also the
other methods of the repository. For testing this we would rather have a
repository (that is database table) already containing some products.
Nothing easier than this. Just add a method CreateInitialData to the
test class as follows
现在我们准备实现数据栈的其它方法。为了测试这部分我们最好有一个数据栈(也就是数据库表)已经包含了一些产品数据。没什么比这更容易的了。只是添加一个如下所示的 CreateInitialData 测试方法
private readonly Product[] _products = new[]
{
new Product {Name = "Melon", Category = "Fruits"},
new Product {Name = "Pear", Category = "Fruits"},
new Product {Name = "Milk", Category = "Beverages"},
new Product {Name = "Coca Cola", Category = "Beverages"},
new Product {Name = "Pepsi Cola", Category = "Beverages"},
};
private void CreateInitialData()
{
using(ISession session = _sessionFactory.OpenSession())
using(ITransaction transaction = session.BeginTransaction())
{
foreach (var product in _products) session.Save(product);
transaction.Commit();
}
}
Call this method from the SetupContext
method (after the create schema call) and we are done. Now each time
after the database schema is created the database is populated with some
products.
调用此方法从 SetupContext 方法(在创建数据库模之后调用)我们这前已经创建此方法。现在每次我们创建数据库模之后都会填入一此产品记录。
Let‘s test the Update method of the repository with the following code
现在我们用如下代码来测试数据栈的 Update (更新)方法
[Test]
public void Can_update_existing_product()
{
var product = _products[0];
product.Name = "Yellow Pear";
IProductRepository repository = new ProductRepository();
repository.Update(product);
// use session to try to load the product
using (ISession session = _sessionFactory.OpenSession())
{
var fromDb = session.Get<Product>(product.Id);
Assert.AreEqual(product.Name, fromDb.Name);
}
}
When running for the first time this code will fail since the Update method has not yet been implemented in the repository. Note: This is the expected behavior since in TDD the first time you run a test it should always fail!
当我们初次运行此方法时会失败因为 Update 方法还没有在数据栈里被实现。
注意:所有的测试初次运行都将失败,这是 TDD (测试驱动开发)期望的方式。
Analogous to the Add method we
implement the Update method of the repository. The only difference is
that we call the update method of the NHibernate session object instead
of the save method.
和我们实现 Add 方法一样我们来实现 repository 中的 Update 方法。唯一不同是我们调用 NHibernate session 对像的 update 方法 而不是 save 方法。
public void Update(Product product)
{
using (ISession session = NHibernateHelper.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
session.Update(product);
transaction.Commit();
}
}
Run the test again an watch it succeed.
再次运行测试就会成功。
The delete method is straight forward.
When testing whether the record has really been deleted we just assert
that the value returned by the session‘s get method is equal to null.
Here is the test method
删除方法也很简单。当测试记录是否被删除时我们只要断言 session 的 get 方法的返回值为 null 就可以。
[Test]
public void Can_remove_existing_product()
{
var product = _products[0];
IProductRepository repository = new ProductRepository();
repository.Remove(product);
using (ISession session = _sessionFactory.OpenSession())
{
var fromDb = session.Get<Product>(product.Id);
Assert.IsNull(fromDb);
}
}
and here the implementation of the Remove method in the repository
这是 repository 的 Remove(删除)方法的实现
public void Remove(Product product)
{
using (ISession session = NHibernateHelper.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
session.Delete(product);
transaction.Commit();
}
}
Querying the Database - 数据查询
We still have to implement the three
methods which query the database for objects. Let‘s start with the most
easy one, the GetById. First we write the test
我们还没有实现能转换成对像的三个数据库查询方法。让我们从最简单的 GetById 开始,首先我们来写这个测试方法
[Test]
public void Can_get_existing_product_by_id()
{
IProductRepository repository = new ProductRepository();
var fromDb = repository.GetById(_products[1].Id);
Assert.IsNotNull(fromDb);
Assert.AreNotSame(_products[1], fromDb);
Assert.AreEqual(_products[1].Name, fromDb.Name);
}
and then the code to fulfill the test
然后完成下面代码的来满足测试
public Product GetById(Guid productId)
{
using (ISession session = NHibernateHelper.OpenSession())
return session.Get<Product>(productId);
}
Now that was easy. For the following
two methods we use a new method of the session object. Let‘s start with
the GetByName method. As usual we write the test first
那现在很容易了。对于下面的两个方法我们 session 对像的新方法。我们从 GetByName 方法开始。照例我们先来写测试方法
[Test]
public void Can_get_existing_product_by_name()
{
IProductRepository repository = new ProductRepository();
var fromDb = repository.GetByName(_products[1].Name);
Assert.IsNotNull(fromDb);
Assert.AreNotSame(_products[1], fromDb);
Assert.AreEqual(_products[1].Id, fromDb.Id);
}
The implementation of the GetByName
method can be done by using two different approaches. The first is using
HQL (Hibernate Query Language) and the second one HCQ (Hibernate
Criteria Query). Let‘s start with HQL. HQL is a object oriented query
language similar (but not equal to) SQL.
GetByName 方法可以用两种不同的方法实现。第一种方式是使用 HQL(Hibernate Query
Language,Hibernate 查询语言)第二种则是用 HCQ(Hibernate Criteria Query,Hibernate
条件查询)。我们先来用 HQL 。HQL 是面向对像的查询语言类似(但确不同于)SQL。
public Product GetByName(string name)
{
using (ISession session = NHibernateHelper.OpenSession())
{
string hql = "from Product as p where p.Name=:Name";
Product product = session.CreateQuery(hql)
.SetString("Name",name)
.UniqueResult<Product>();
return product;
}
}
使用此方式需要添加对程序集 Antlr3.Runtime.dll 的引用。此文件位于 NHibernateRequired_Bins 文件夹下
To be added: implemetation of GetByName using HQL. Implement HCQ as below this works as expected and returns a product entity.
上述代码用 HQL 实现 GetByName 方法。稍候我们会用 HCQ 实现相同的方法来返回一个产品实体。
In the above sample I have introduced a commonly used technique when using NHibernate. It‘s called fluent interfaces.
As a result the code is less verbose and easier to understand. You can
see that a HQL query is a string which can have embedded (named)
parameters. Parameters are prefixed by a ‘:‘. NHibernate defines many
helper methods (like SetString used in the example) to assign values of
various types to those parameters. Finally by using UniqueResult I tell
NHibernate that I expect only one record to return. If more than one
record is returned by the HQL query then an exception is raised. To get
more information about HQL please read the online documentation.
在上面的实例中,我采用了使用 NHibernate 时的一种普遍运用的技术。它被称作流畅接口。
所以代码看起来既不繁琐又易理解。你能看到一个 HQL 查询是一个嵌入了(命名的)参数的字符窜。参数是以“:" 冒号为前辍。NHibernate
定义了许多助手方法(像在本例中所使用的 SetString )用来给那些参数设定不同类型值。最后通过使用 UniqueResult 我告诉
NHibernate 我希望仅返回一条记录。如果通过 HQL 查询返回记录超过一条就会引发异常。想知道得多有关于 HQL 的信息请参阅 在线文档。
The second version uses a criteria
query to search the requested product. You need to add a reference to
NHibernate.Criterion on your repository page.
第二个版本我们会使用条件查询用来查找所需产品。你需要在你的 repository 代码页里添加一个对命名空间 NHibernate.Criterion 的引用(Restrictions 类位于此命名空间)。
public Product GetByName(string name)
{
using (ISession session = NHibernateHelper.OpenSession())
{
Product product = session
.CreateCriteria(typeof(Product))
.Add(Restrictions.Eq("Name", name))
.UniqueResult<Product>();
return product;
}
}
Many users of NHibernate think that
this approach is more object oriented. On the other hand a complex query
written with criteria syntax can quickly become difficult to
understand.
许多 NHibernate 用户认为这种方法过于面向对象了。还有复杂的查询语句用条件语法来写的话会很容易变得难于理解。
The last method to implement is GetByCategory. This method returns a list of products. The test can be implemented as follows
最后要实现的方法是 GetByCategory 。这个方法会返回一个产品列表。测试方法的实现如下
[Test]
public void Can_get_existing_products_by_category()
{
IProductRepository repository = new ProductRepository();
var fromDb = repository.GetByCategory("Fruits");
Assert.AreEqual(2, fromDb.Count);
Assert.IsTrue(IsInCollection(_products[0], fromDb));
Assert.IsTrue(IsInCollection(_products[1], fromDb));
}
private bool IsInCollection(Product product, ICollection fromDb)
{
foreach (var item in fromDb)
if (product.Id == item.Id)
return true;
return false;
}
and the method itself might contain the following code
方法本身可能包含如下代码
public ICollection GetByCategory(string category)
{
using (ISession session = NHibernateHelper.OpenSession())
{
var products = session
.CreateCriteria(typeof(Product))
.Add(Restrictions.Eq("Category", category))
.List();
return products;
}
}
Summary - 小结
In this article I have shown you how to
implement a basic sample domain, define the mapping to a database and
how to configure NHibernate to be able to persist domain objects in the
database. I have shown you how to typically write and test CRUD methods
for your domain objects. I have taken MS SQL Compact Edition as sample
database but any other supported database can be used (you only have to
change the hibernate.cfg.xml file accordingly). Ee have no dependencies
on external frameworks or tools other than the database and NHibernate
itself (.NET of course never counts here).
本文我为你说明了怎样实现一个基本的简单的 domain(领域),定义了对数据库的映射和怎样配置 NHibernate
用以能够持久化数据库中的 domain 对象。我也为你说明的怎样给 domain 对象写典型的 CRUD 方法及测试方法。我采用了 MS SQL
Compact Edition 作为数据库实例,但是任何其他支持的数据库也可以使用(你只要相应的更改 hibernate.cfg.xml
文件)。除了数据库和 NHibernate 自身外我们没有依赖于外部框架或工具(.NET 当然不会算在内)。
官方内容有几处遗漏,本文做了补充说明,所提供的示例全部通过测试,由于本人水平有限,难免出现错误之处,还请各位业内人士批评指正、不吝赐教!
感谢咏洁对我的支持!