zoukankan      html  css  js  c++  java
  • 【翻译】首个基于NHibernate的应用程序

    首个基于NHibernate的应用程序

     Your first NHibernate based application

    英文原文地址:http://www.nhforge.org/wikis/howtonh/your-first-nhibernate-based-application.aspx

    翻译原文地址:http://www.cnblogs.com/13yan/p/5671072.html

     

    本文涉及到的DEMO下载

     

    定义领域模型

     

    让我们开始通过定义一个非常简单的领域模型。目前它是由一个称为产品的实体。该产品具有 3 个属性:名称、 类别和中止。

     

    添加一个文件夹 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; }

     }

    }

    现在我们想要能够持久化相关数据库中此实体的实例。我们选择了 NHibernate来完成这一任务。

    领域模型中实体的一个实例对应数据库表中的行。所以我们必须在数据库中定义实体和相应的表之间的映射。此映射可以是另外定义一个映射文件 (一个xml 文档) 或装饰的实体和属性,可以完成此映射。随后,我将开始映射文件的定义。

     

    译者的话:装饰的实体是什么?目前我们知道除了xml文件映射,还有Fluent NHibernate的Mapping和特性(attribute,类似Java中注解@),装饰可能是指他们的统称吧。

    定义映射

     

    创建一个文件夹 Mappings 到 FirstSample 项目中。并在该文件夹中添加一个新的 xml 文档并命名为 Product.hbm.xml。请注意"hbm"是文件名称的一部分。这是一项约定,这个约定用于NHibernate 自动识别这个文件为一个映射文件。右键此 xml 文件点击属性,在生成操作一项定义"嵌入的资源"

     

    在 Windows 资源管理器中找到 nhibernate mapping.xsd,它在 NHibernate 的 src 文件夹中,并将其复制到您的 SharedLibs 文件夹中。编辑 xml 映射文件时,在VS菜单中的XML-架构中导入此xsd文档。VS 然后将智能感知和验证。

     

    回到在 VS 将架构添加到 Product.hbm.xml 文件

     

    让我们从现在开始。每个映射文件都须定义一个 <hibernate-mapping> 根节点。

    <?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>

    在映射文件引用领域模型类时你一定要提供的类的完全限定的名称(如 FirstSample.Domain.Product , FirstSample)。若要使 xml 不那么繁琐,你可以定义程序集名称和领域模型类的命名空间,到根节点的两个属性:assembly 和namespace。它是类似于使用 C# 中的声明。

     

    现在,我们必须先为产品实体定义一个主键。技术上我们可以拿产品的名称属性作为主键,因为此属性必须定义,并且必须是唯一的。但通常会使用代理键代替它成为主键。因此我们将添加一个名为Id的属性到我们的实体。我们使用 Guid 作为 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; }

     }

    }

    完整的映射文件

    <?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 不会以我们的方式,比如,它定义了很多合理的默认值。所以,如果您不显式地提供属性的列名,它将按属性名去对应列名。或 NHibernate从类的定义中,可以自动推断的表名或列名。因此我的 xml 映射文件不会被堆满冗余信息。有关于映射文件更详细的解释请参阅在线文档。你可以在这里找到它。

     

    你解决方案资源管理器现在应该看起来像这样 (Domain.cd 包含我们简单的领域模型类图)

     

    译者的话:

     

    配置 NHibernate

     

    我们现在必须告诉 NHibernate 我们想要使用哪个数据库产品,并提供详细的链接信息,以连接字符串的形式。NHibernate 支持许多数据库产品 !

     

    向 FirstSolution 项目中添加一个新的 xml 文件,并命名为 hibernate.cfg.xml。将其属性"复制到输出目录"设置为"始终复制"。由于我们引用了SQL Server Compact Edition数据库在first sample项目中,所以输入以下信息到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>

    使用此配置文件,我们告诉 NHibernate 我们想要使用 MS SQL Server Compact Edition作为我们的目标数据库和数据库的名称是 FirstSample.sdf (= 连接字符串)。我们同时也定义了希望看到NHibernate生成并发送到数据库的 SQL语句 (在开发过程中调试时强烈推荐启用此定义)。仔细检查你的代码中有没有错别字 !

     

    添加一个叫FirstSample.sdf的空的数据库,到 FirstSample 项目 (选择本地数据库作为模板)

     

    单击添加并忽略数据集创建向导 (就是点击取消)。

     

    译者的话:我们不一定安装过MS SQL Server Compact Edition数据库,我将在Demo中把它替换成SQLite和相应的配置,这样我们就不需要为了这个快速入门而去专门找一个数据库了。

     

    测试设置

     

    是时候来测试我们的安装了。首先验证您的 SharedLibs 文件夹中有以下文件

     

    您可以找到Microsoft SQL Server Compact Edition在你的程序文件夹中目录最后 8 个文件。

     

    注︰ System.Data.SqlServerCe.dll 位于子文件夹中的桌面。

     

    所有其他文件可以在NHibernate 文件夹中找到。

     

    在您的测试项目中添加对 FirstSample 项目的引用。另外测试项目引用 NHibernate.dll、 nunit.framework.dllSystm.Data.SqlServerCe.dll (记得要引用位于 SharedLibs 文件夹中的文件 !)。要注意为设置属性"复制本地"为 true 为 System.Data.SqlServerCe.dll, 因为在默认情况下它设置为 false !

     

    译者的话:现在VS2012以上都有自带的单元测试项目,也非常好用。所以无需引用nunit.framework.dll,同样System.Data.SqlServerCe.dll也可以替换成System.Data.Sqlite.dll。

     

    在测试项目中添加一个类,命名为 GenerateSchema_Fixture。

     

    现在将下面的代码添加到 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);
      }
     }
    }

    测试方法的第一行创建 NHibernate 配置类的一个新实例。此类用于配置 NHibernate。在第二行,我们告诉 NHibernate 配置本身。NHibernate 将留心配置信息,因为我们没在测试方法中提供任何信息。所以 NHibernate 将搜索输出目录中的 hibernate.cfg.xml 文件来调用。这正是我们为什么要在这个文件中这么设置的原因。

     

    在第三行的代码,我们告诉 NHibernate 它可以发现并包含Product类的程序集的映射信息。它将在嵌入的资源中只找到一个(Product.hbm.xml)这样的文件。

     

    第四行代码使用NHibernate中 SchemaExport 的工具类,为我们在自动生成数据库中的架构。

     

    注︰ 我们先不用去理解此测试方法中NHibernate 如何工作 , 但应当关注是否正确地安装。

     

    如果你有安装的 TestDriven.Net 你可以现在只是右键点击里面的测试方法并选择"运行 Test(s)"来执行测试。

     

    译者的话:VS2012以上版本的单元测试可以不用TestDriven.Net和NUnit,微软有自带的。

     

    如果每一件事是好的你应该看到下面的结果,在输出窗口

     

    译者的话:

     

    如果你有安装 ReSharper 你可以开始测试通过单击黄色绿色圆圈的左边框,选择运行。

     

    其结果是,如下所示

     

    译者的话:原文没图,我们还是用VS自带的吧,如下图

    在出现问题时

     

    如果你测试失败,请仔细检查你的目标目录,在其中找到下列文件 (即︰ m:devprojectsFirstSolutionsrcFirstSolution.Testsindebug)

     

    仔细检查NHibernate 配置文件 (hibernate.cfg.xml) 中或在映射文件 (Product.hbm.xml)中是否有错别字,最后检查映射文件 (Product.hbm.xml)是否设置为"嵌入的资源"的"生成操作"。如果测试成功,才继续。

     

    我们第一次的 CRUD 操作

     

    现在很明显我们的系统已是准备好开始了。我们成功地实现了我们的领域模型,定义映射文件和配置 NHibernate。最后我们使用 NHibernate 从我们的领域模型 (和我们映射文件) 自动生成数据库架构。

     

    在 DDD (参考Eric Evans的《领域驱动设计》) 的精神,我们为所有的 crud 操作(创建、 读取、 更新和删除)定义了Repository。Repository接口是领域模型不实现的一部分!执行是特定的基础设施。我们要保持我们的领域模型和持久化无关 (PI)。

     

    译者的话:这一段我不知道该如何去翻译它,但我可以解释它的意思。它的大致意思是根据DDD的思想,领域模型Domain里面不应该有和持久化有关的东西,比如我们的Product中不该包含数据库CRUD操作,而这些CRUD的基础操作该在仓储Repository接口中实现。

     

    到我们的 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<Product> GetByCategory(string category);
     }
    }

    添加一个类 ProductRepository_Fixture 到测试项目下,并添加下面的代码

    [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();
      }
     }

    在 TestFixtureSetUp 方法的第四行,我们创建一个session factory。这是一个开销很大的过程,因此程序运行期间应该只执行一次。这就是为什么把它放到这种测试期间只执行一次的方法的原因。

     

    要保持我们测试方法无副作用,每个测试方法执行之前,我们重新创建我们的数据库架构。因此我们添加下面的方法

    [SetUp]

      public void SetupContext()

      {

       new SchemaExport(_configuration).Execute(false, true, false, false);

      }

    译者的话:NHibernate3.0中,只有3个参数。new SchemaExport(cfg).Execute(false,true,false);

    现在我们可以实现向数据库中添加一个新的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();
      }
    }
    }

    操作数据

     

    现在回到ProductRepository_Fixture测试类和实现第一个测试方法

      [Test]

      public void Can_add_new_product()

      {

       var product = new Product {Name = "Apple", Category = "Fruits"};

       IProductRepository repository = new ProductRepository();

       repository.Add(product);

      }

    首次运行的测试方法将失败,因为我们的仓储类未实现 Add 方法。让我们实现它。但是,等一等,我们必须首先定义一个小的Helper类提供我们NHibernate 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();
      }
    }
    }

    运行期间,不管客户端何时需要一个新的session,此类只创建session factory第一次。

     

    现在我们可以定义 Add 方法在 ProductRepository 中,如下所示

    public void Add(Product product)

      {

       using (ISession session = NHibernateHelper.OpenSession())

        using (ITransaction transaction = session.BeginTransaction())

        {

         session.Save(product);

         transaction.Commit();

        }

      }

    第二次运行的测试方法会再次失败并显示以下消息

     

    译者的话:

     

    这是因为 NHibernate 是默认情况下配置为使用延迟加载的所有实体。这是推荐的方法,我强烈建议不要更改,为了最大的灵活性。

     

    我们怎样才能解决这个问题?很容易,让领域模型中所有属性 (方法) 加上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; }

     }

    现在再次运行测试。它应该会成功,我们得到以下输出

     

    译者的话:

     

    请注意NHibernate输出的 sql 语句。

     

    现在我们已经成功地向数据库插入一个新的Product。但让我们测试它是否真的是这样。让我们来扩展我们的测试方法

    [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);
       }
      }

    再次运行测试。希望它会成功......

     

    现在我们准备也实现repository中的其他方法。为了测试这我们宁愿要一个repository  (即数据库表) 已经包含了一些产品。没有什么比这更简单。只是添加 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();
        }
      }

    (在创建架构调用后) 从 SetupContext 方法调用此方法。现在每次数据库架构创建数据库后填充一些产品。

    让我们测试用下面的代码库的更新方法

    [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);
       }
      }

    第一次运行时此代码将失败,因为Update方法尚未在Repository中实现。注︰ 这是预期的行为,因为在 TDD 第一次运行测试时它应该总是失败 !

     

    译者的话:这篇快速开始的入门教程水有点深,又是DDD,又是TDD,吓死人了,没接触过的人可以忽略。同时也可见NHibernate更多是面向一些资深的面向对象程序员,可悲的是很多程序员未入门时就接触到了它。叹息!

     

    类似于 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();

       }

      }

    再次运行测试希望它成功。

     

    Delete 方法是直截了当。测试是否真的已删除记录时,我们只是断言由会话的 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);

       }

      }

    Repository中删除方法的实现

      public void Remove(Product product)

      {

       using (ISession session = NHibernateHelper.OpenSession())

        using (ITransaction transaction = session.BeginTransaction())

        {

         session.Delete(product);

         transaction.Commit();

        }

      }

    查询数据库

     

    我们仍然必须执行查询的数据库对象的三个方法。我们先从最容易的一个,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);

      }

    然后完成测试的代码

     

      public Product GetById(Guid productId)

      {

       using (ISession session = NHibernateHelper.OpenSession())

        return session.Get<Product>(productId);

      }

    现在,那很简单。为以下两种方法,我们使用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);

      }

    GetByName 方法的实现可以通过使用两个不同的方法。第一使用 HQL (Hibernate Query Language) 和第二个 HCQ (Hibernate Criteria Query)。让我们开始使用 HQL。HQL 是面向对象的查询语言 SQL 类似 (但不是等于)。

     

    译者的话:他指的第一种方法HQL是这个样子的。

     

    在上面的示例中我介绍了常用的技术使用 NHibernate 时。它被称为fluent接口。作为结果的代码是简练也更易于理解。你可以看到一个 HQL 查询是一个字符串,它可以具有嵌入 (命名) 参数。参数使用前缀 ':'。NHibernate 定义很多的helper方法 (如示例中使用 SetString),将各种类型的值分配给这些参数。最后通过使用 UniqueResult 我告诉 NHibernate 希望只有一条记录返回。如果多个然后引发异常,HQL 查询将返回一条记录。要获取更多的信息 HQL 请阅读在线文档。

     

    第二个版本使用criteria query来搜索请求的Product。

      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;

       }

      }

    NHibernate 的许多用户认为这种做法是更多面向的对象。在另一方面编写的criteria语法复杂查询可以迅速成为难以理解。

     

    实现的最后一个方法是 GetByCategory。此方法返回Product的列表。测试可以实现,如下所示

    [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<Product> fromDb)

      {

       foreach (var item in fromDb)

        if (product.Id == item.Id)

         return true;

       return false;

      }

    方法本身可能包含下面的代码

      public ICollection<Product> GetByCategory(string category)

      {

       using (ISession session = NHibernateHelper.OpenSession())

       {

        var products = session

         .CreateCriteria(typeof(Product))

         .Add(Restrictions.Eq("Category", category))

         .List<Product>();

        return products;

       }

      }

    摘要

     

    在这篇文章中我已经给你如何实现基本示例领域模型,定义映射到数据库以及如何配置 NHibernate 能够持久化领域对象在数据库中。我给你展示了如何通常编写和测试您的领域对象的 CRUD 方法。我拿MS SQL Compact Edition 作为示例数据库,但可以使用任何其他受支持的数据库 (你只需要相应地更改 hibernate.cfg.xml 文件)。我们没有依赖于外部框架或工具以外的数据库和 NHibernate 本身 (.NET 当然从来没有计算在内)。

    译者的话:终于翻译完了,这篇快速开始非常适合初学者,因为提供的例子是可以被实现的,而且可以同时入门DDD和TDD,看得出作者非常用心。而我也在其中加入了批注和补充了显示不了的图片。

     

  • 相关阅读:
    Droppable(放置)组件
    Draggable(拖动)组件
    1.引入必要的文件 2.加载 UI 组件的方式 4.Parser 解析器
    mvc自带的异步表单提交
    MVC,jquery异步
    Container With Most Water
    Palindrome Number
    String to Integer (atoi)
    Merge k Sorted Lists
    Longest Valid Parentheses
  • 原文地址:https://www.cnblogs.com/13yan/p/5671072.html
Copyright © 2011-2022 走看看