如果你正在阅读此文我们假设你刚刚下载了NHibernate的并希望开始使用它。
本教程将讨论如下几个步骤
- 安装NHibernate
- 定义一个简单的业务对象类。
- 创建一个NHibernate的映射加载和保存业务对象。
- 配置NHibernate与本地数据库进行连接。
- 自动生成一个数据库。
- 使用Repository模式编写简单的CRUD代码。
- 使用单元测试以确保代码工作正常。
这是我们期待的最终结果
但首要的事情是
让我们从您刚刚下载的ZIP文件开始。
安装NHibernate的
如果您已经下载了NHibernate的二进制压缩文件你需要做的就是将该文件解压缩到合适的地方。我通常创建一个文件夹名为SharedLibs c:\Code\SharedLibs\NHibernate并把压缩文件解压到那里。 But whatever youre 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安装好了容易嗯。 Well 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, well 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到此文件夹。代码是非常简单使用自动属性一新的C3.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文件添加架构文档。
让我们现在就开始。 每个映射文件必须定义一个根节点
1:
2: 4: 5: 映射文件中引用一个领域类时你总是必须提供类如FirstSample.Domain.ProductFirstSample完全限定名。 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. Its 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: 2: 4: 5: 6: 7: 8: 9: 10: 11: 12: NHibernate的没有得到我们的方式例如它定义了许多合理的默认值。 So if you dont 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的支持许多数据库产品 添加一个新的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: 2: 3: 4: NHibernate.Connection.DriverConnectionProvider 5: NHibernate.Dialect.MsSqlCeDialect 6: NHibernate.Driver.SqlServerCeDriver 7: Data SourceFirstSample.sdf 8: true 9: 10: 有了这个配置文件我们告诉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.dllnunit.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为“ 嵌入的资源 ”。 如果测试成功那么继续以下操作。 现在很明显我们的系统已准备好开始。我们成功地落实我们的域名明确了映射文件和配置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 GetByCategory(string category); 13: } 14: } 添加一个类ProductRepository_Fixture到测试项目中并添加以下代码 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 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.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.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.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(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方法的实现可以通过两种不同的方法。第一个是用HQLHibernate查询语言和第二个HCQHibernate 条件查询。 让我们先从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(); 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 fromDb) 11: { foreach (var item in fromDb) 12: if (product.Id item.Id) 13: return true; 14: return false; 15: } 该方法本身可能包含以下代码 1: public ICollection 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(); 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