NHibernate 快速入门
这篇教程将透过你的第一个NHibernate项目,讲述配置和映射所需要的基本步骤。
仅关注我们想要的,我们将从一个很简单的例子开始。为了让代码量最小,我们也去掉了单元测试和接口的代码,专一地关注持久化数据的代码并阐述一些基本的CRUD(Change, Read, Update, Delete) 技术。
这篇教程和其他样例中要用到的工具:
Microsoft SQL Server 2005 Express Edition - 存放我们的持久化数据。这篇教程在SQL Server 2008环境下也有效。在这篇教程中,我假设你有一个本地SQL Server的实例并且熟练地使用SQL Manger Studio。
NHibernate version 2.1.2 - 你可以点击下方链接下载最先的 NHibernate: http://sourceforge.net/projects/nhibernate/files/NHibernate/
Microsoft Visual Studio 2008 - 我们的开发环境。
创建项目文件
在这个例子里,因为我们不关注用户界面 ,所以我们将从创建一个新的控制台应用程序项目开始:
根据这篇教程的目的,我们把我们的应用程序取名叫'HelloNHibernate'。我也推荐坚持这种命名的传统,并遵循不同的样例使用不同的名字,尽管在很多情形下,教程的其他部分也会使用这样的命名。
添加对NHibernate的引用
我们下一步要添加一系列让NHibernate发挥作用的必须添加的引用。当你下载并解压你的NHibernate安装包后,电脑上就会创建一些目录,包括 'Required_bins'和'Required_for_LazyLoading。
首先,你要把 Required Bins目录下所有的DLL包含进来 - 它们代表NHibernate使用的核心组件。
其次,我们把Required_for_LazyLoading的一个子目录下所有的Dll包含进来。选择哪个目录的Dll来完成延迟加载,最终将取决于你。 在这篇教程和其他文章中,我使用的是Castle dynamic proxy。
配置NHibernate
建立好我们的引用之后,我们现在需要创建和更新我们的App.config (如果是网站,就修改Web.config)。在我们的项目中,我做以下几个假设:
-
我们安装了SQL Server 2005并创建好了数据库.
-
数据库开启了信任连接
-
使用Castle proxy factory
-
把TSQL语句输出到控制台(在你学习NHibernate的时候,我强烈推荐你这样做,因为你可以看到后台正在做什么).
这是一个App.config配置的例子,它包含了这篇教程中所需要的部分。
<?xmlversion="1.0"encoding="utf-8" ?> <configuration> <configSections> <sectionname="hibernate-configuration"requirePermission="false"type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" /> </configSections> <hibernate-configurationxmlns="urn:nhibernate-configuration-2.2"> <reflection-optimizeruse="false" /> <session-factory> <propertyname="connection.provider">NHibernate.Connection.DriverConnectionProvider</property> <propertyname="dialect">NHibernate.Dialect.MsSql2005Dialect</property> <propertyname="connection.driver_class">NHibernate.Driver.SqlClientDriver</property> <propertyname="connection.connection_string">Data Source=(local); Initial Catalog=quickstart; Trusted_Connection=true;</property> <propertyname='proxyfactory.factory_class'>NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property> <propertyname="show_sql">true</property> </session-factory> </hibernate-configuration> </configuration>
在配置中,你将看到我们创建NHibernate来使用SQL Server和SQL 2005的语句。你也能看到连接字符串(可以根据需要修改),还有对Castle Proxy Factory的引用,也有一个告诉Nhibernate输出TSQL语句到控制台的标志(show_sql)。
创建我们的模型
NHibernate允许你直接使用Plain Old CLR Objects (POCOs),而不用通过存储程序来直接和数据库交互。使用POCOs的一个优势在于我们不用绑定特定的持久化层。举个例子,有些ORM解决方案需要特殊属性,或者是基于你的模型对象,这些对象又是从特定的基类中继承而来的。
在NHibernate中,不用特殊的修饰,就可以让我们的对象和持久化层交互。在NHibernate中,我们唯一要意识到的是所有的我们希望持久化的属性必须是虚拟的,并且要开启延迟加载,所有类中的公共方法必须是虚拟的,哪怕它们并没有包含到我们的映射文件中。
通常来讲,最好把所有的属性都设置为虚拟的,这样你的基类就封装了,不管你使用延迟加载与否。
另外,我们可以让POCO里的属性和方法不持久,并且可以让多个POCOs映射到数据库中相同的数据表。举个例子,我们有一个POCO提供了产品的详情,包括图片和描述,其他的轻量版本只包括用来创建商品清单的名称和数量。
在我们的教程中,我们将创建一个极端的简单对象。在后期的教程中,我们会讲述更加复杂的话题,比如父子关系,高级搜索技术等等。
创建在你的项目中创建一个类,命名为‘Produce’,并设置如下的属性。注意Nhibernate是大小写敏感的,所以我们的属性名要准确地匹配后面要创建的Nhibernate映射。
namespace HelloNHibernate { public classProduct { public virtual int? ProductId { get; set; } public virtual string ProductName { get; set; } } }
注意我们设置ProductId为可为null的值。这允许我们创建一个没有ID的实例,反过来告诉NHibernate这是一个新对象而不是一个ID为'0'的对象。
创建NHibernate映射文件
Nhibernate使用XML映射文件来映射POCO到数据库对象。虽然在很多案例中这可能是一对一关系,但这并不是必要的。你可以轻易地更改字段名称并创建不同的映射来给数据库对象创建可选的表示区域。
举个例子,我们有一个复杂的'Product'对象,它包含了大量的文本信息,一个与订单和复杂清单数据的容易。这对于我们正在编辑的单个产品的产品详情区域来说,将是一个很好的映射,它将变得十分臃肿,以至于在产品的下拉列表中,它不能作为一个强类型列表的成员。在后面,要创建一个更简单一点到只有产品ID和产品名称的持久化区域的映射。
在我们的教程中,我们使用一个简单的直接关系到我们上面创建的POCO的映射。来创建这样的一个映射,我们要添加一个新的XML文件到上面展示的工程中。当创建XML文件时,要让它以.hbm.xml命名方式结尾-这种传统告诉NHibernate这是一个映射文件。我们的名称叫做'Product.hbm.xml'。
你的XML文件应该包含以下信息。下面我们将一一讲述每一个标签:<?xmlversion="1.0"encoding="utf-8" ?> <hibernate-mappingxmlns="urn:nhibernate-mapping-2.2" assembly="HelloNHibernate" namespace="HelloNHibernate"> <class name="Product" table="Products"> <id name="ProductId"> <generatorclass="identity"/> </id> <property name="ProductName"length="50" /> </class> </hibernate-mapping>
-
在hibernate-maping标签中,我们同时引用类集(POCOs)所属的程序集合命名空间。当你的映射文件在单独的命名空间或者程序集而不是模型中,这种方式是十分方便的。
-
class元素表示到单个POCO的映射。name表示上面的程序集和命名空间中的类名,table属性告诉NHibernate数据库中的哪个表或者视图将被映射。
-
id元素告诉NHibernate哪个数据库的字段和对应的对象作为一个唯一键来使用。在本例子中,我们使用ProductId这个字段。
-
generator元素告诉NHibernate怎样给新实体来创建唯一ID。因为我们使用SQL Server属性中的ID属性,所以我们指明generator类为'identity'。
-
property标签是你见得最多的标签。它简单地映射一个到数据表或者视图中对应字段的映射。我已经把这个例子中最长的属性包含进来了,因为我们想为映射创建一个架构。对于我们所需要的产品名称,默认的255个字符已经足够大了。
一旦XML文件创建好了,你需要确保它被设置为嵌入式资源,否则NHibernate不会读出你的XML文件,那么映射就不会生效了。完成这一步,你要在解决方案资源浏览器中点击你新建的XML文件,更改XML的生成方式。
到这一步,你的解决方案应该和下图类似:
使用 NHibernate连接数据库
到了这一步,我们已经完成了安装和配置,现在就准备使Nhibernate和我们的数据库来交互。
透过这些例子,我推荐你单独跟着做,并观察对象是如何发生改变的,数据又是如何持久化到数据库的。在你观察持久化对象改变的时候,这有助于你更好地理解NHibernate是如何工作的,有助于你巩固下面的样例。
下面到了代码了!在第一个样例中,我们只关注使用NHibernate来完成持久化数据所需要的最小量的代码,而不关注测试和其他更好的实践,注意到这些事很重要的。当我们引申到更复杂的样例中,我们要花费更多的时间为你的实际程序来创建运行更好的,更加健壮的架构。
在NHibernate中,我们的根对象将是一个实现ISessionFactory接口的对象。一个SessionFactory是一个很昂贵的对象,在你的程序中,你只有在需要一个就够了。
要创建我们的SessionFactory,我们将使用NHibernate Configuration对象的一个方法来创建一个基于App.config配置中的会话工厂,并创建不同映射文件。
让我们来看看一些代码:
using NHibernate; using NHibernate.Cfg; using NHibernate.Tool.hbm2ddl; namespace HelloNHibernate { class Program { static void Main(string[] args) { Configuration config= new Configuration(); config.AddAssembly(typeof(Product).Assembly); ISessionFactory sessionFactory = config.BuildSessionFactory(); var schema = new SchemaExport(config); schema.Create(true, true); } } }
一一来讲述前5行代码.
第一行代码基于app.config文件中的值创建了一个NHibernate COnfiguration对象的实例,这个对象在命名空间NHibernate.cfg中。
Configuration config= new Configuration();
接下来,我们要加载映射文件,它们用来把POCOs映射到持久化数据库对象。加载映射文件有很多方法,但是所有的方法都需要传递一个程序集作为参数。NHibernate用这些程序集来查找所有的持久化映射数据。
在第一个样例中,我们仅仅使用Product对象,所以我们可以用下面这行代码。(另外,我们也可以用Assembly.GetExecutingAssembly(),因为Product hbm 文件和类对象都在主程序集中。)
config.AddAssembly(typeof(Product).Assembly);
然后我们使用Configuration对象的BuildSessionFactory()方法创建会话工厂。这个函数返回一个实现我们要使用的ISeesionFactory的对象。
ISessionFactorysessionFactory = config.BuildSessionFactory();
下面两行代码用来重构我们的架构。这些命令可以用来引导一个新应用程序的安装,清除集合测试期间的数据库,或者输出DDL到一个文件用来给DBA提供一个脚本。
我们用这种方法来重置样例程序执行中的数据库,有了这样的代码,你不用担心数据库的清理工作。
var schema = newSchemaExport(config); schema.Create(true, true);
(你应该看看控制台下的SQL语句。)
if exists (select * from dbo.sysobjects where id = object_id(N'Products') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table Products create table Products ( ProductId INT IDENTITY NOT NULL, ProductName NVARCHAR(50) null, primary key (ProductId) ) )
使用NHibernate实现CRUD基本操作
现在我们可以通过一个有效的SessionFactory对象来建立我们的数据存储,是时候存储一些数据了。
第一个例子,我们要完成product表中基本的CRUD操作。我们将从下面的代码片段开始(你可以在schema.Create之后立即添加这些代码),然后分别详细地讲述每个操作。
ProductpNew = newProduct() {ProductName = "Canned Salmon"}; using (var session = sessionFactory.OpenSession()) { //Create a Product using (var trans = session.BeginTransaction()) { session.Save(pNew); trans.Commit(); } //Retrieve a product by ID ProductpGet = session.Get<Product>(pNew.ProductId); //Update a Product pGet.ProductName = "Canned Tuna"; using (var trans = session.BeginTransaction()) { session.Update(pGet); trans.Commit(); } //Delete a Product using (var trans = session.BeginTransaction()) { session.Delete(pNew); trans.Commit(); } }
从创建product ( Canned Salmon)开始。我们让ProductId置空-这是因为这是一个ID字段,我们还没有持久化这个对象。就像你所看到的,NHibernate不仅处理数据库的持久化,还为我们设置这个属性到新创建的ID值。(这是一个我们以往要用从存储程序中返回的@@Identity变量来完成的一个任务。)
接下来,从SessionFactory中打开一个Session.所有的NHibernate持久化操作将会在Session的上下文中处理。
using (var session = sessionFactory.OpenSession())
完成会话创建后,我们可以来谈谈业务。在检索数据的情况下,使用Session对象是不错的选择。但是在我们需要在数据库中更改数据的情形下,我们要使用从当前Session对象创建的Transaction对象(当我们创建,更新,删除product对象的时候,你会看到这一点。)
创建一个持久化对象
Wh不管我们有一个还是多个属性,创建新的持久化对象遵循相同的模式,并能用几行代码就能完成。
- 为你要更新的数据创建一个转化
- 在当前会话中执行Save方法,把你想要持久化的对象作为参数来传递
- 提交转化
//Create a Product using (var trans = session.BeginTransaction()) { session.Save(pNew); trans.Commit(); }
(你应该在控制台下看看下面的代码)
NHibernate: INSERT INTO Products (ProductName) VALUES (@p0); select SCOPE_IDENTITY();@p0 = 'Canned Salmon'
如果你看看pNew的属性,你会看到Nhibernate已经更新基于持久化数据库实体的对象了(本例子中,将ProductId设为'1')。
通过ID检索持久化对象
从数据库中检索对象时,NHibernate提供了一种简单的方法,如果知道了检索的实体的ID号。在这里,创建一个新对象(pGet)基于刚添加到数据库(pNew)中的对象的ID。
//Retrieve a product by ID ProductpGet = session.Get<Product>(pNew.ProductId);
有一件重要的事情要注意,在会话中,pGet和pNew指向相同的持久化后的实体,所以改变任何一个对象(pGet或者pNew)都会改变对象中的反射。
你同样要注意,控制台中的输出中没有'Select'语句。这是因为我们能够查找在会话中已经加载的对象(pNew)来连接pGet。
另一方面,如果我们想在不同的会话上下文中查找pGet,有两种情况是不同的。
第一,pNew和pGet不再引用内存中的相同对象(尽管它们还是绑定到数据库中相同的持久化对象)。第二,你将会在控制台中看到Select语句。因为我们将会创建一个新会话,这个会话正是NHibernate用数据库中值为'1'的ID检索实体时所需要的。
NHibernate: SELECT product0_.ProductId as ProductId0_0_, product0_.ProductName as ProductN2_0_0_ FROM Products product0_ WHERE product0_.ProductId=@p0;@p0 = 1
更新一个持久化对象
同创建持久化对象一样,我们更新对象是在转化上下文中的一个对象之前执行一个方法,如下所示:
//Update a Product pGet.ProductName = "Canned Tuna"; using (var trans = session.BeginTransaction()) { session.Update(pGet); trans.Commit(); }
(你在控制台会看到以下代码)
NHibernate: UPDATE Products SET ProductName = @p0 WHERE ProductId = @p1;@p0 = 'Canned Tuna', @p1 = 1
会话对象同样有一个非常方便的'SaveOrUpdate'方法,它会自动地执行恰当的语句。比如,如果POCO的ID为空,会执行插入操作。如果不为空,会执行更新操作。
删除一个持久化对象
Fo最后一个例子,我们将删除一个product。同样,因为这会影响到我们的持久化实体,我们将在转化的作用域中执行删除操作,并传递我们希望删除的POCO最为参数。
//Delete a Product using (var trans = session.BeginTransaction()) { session.Delete(pNew); trans.Commit(); }
(你在控制台中会看到以下代码)
NHibernate: DELETE FROM Products WHERE ProductId = @p0;@p0 = 1
总结
虽然这是一个非常简单的例子,以下几点要仔细思考。
-
我们没有创建任何的DDL。大多数评价ORMs的开发者在寻找一个小程序运行方案时,写了海量的TSQL语句。我们并没有彻底地更改我们的POCOs-它们没有属性,接口或者基类。我们唯一的操作就是把属性的值改为Virtual。有了这样的变化,在没有Nhibernate的情况下,我们可以轻易地使用相同的对象,这就提高了柔韧性和易测性。
-
我们已经完成了要创建的TSQL的可视性。尽管某些数据库(比如微软的SQL Server)优化存储程序的的能力还存在争议,但在数据库服务中,性能优势最小量地分配到当前的处理能力。
-
另一个有争议的是,好的DBA可能会创建更加优化的代码,比NHibernate创建的代码还要好。
虽然谨慎的手动查询比创建的代码更好,但是大多数时候NHibernate创建的代码远比很多开发者写的程序创建的代码好得多。
举个例子,一个一般的更新程序可能会带来一百个参数,这可能会在数据库记录中潜在地发生改变。NHibernate,另一方面,将会智能地根据动态更新语句来改变实际变化的列,而表现得更好。
在第一个教程中,我们阐述了使用NHibernate连接一个持久化数据资源的资本步骤,和如何实现简单的CRUD操作。在下一系列的教程中,我们将会关注更加复杂的情况,并阐述更好的实践来帮助你实现更好的可测试性和维持性的数据访问层。