NHibernate初学者指南(5):映射模型到数据库之方式一

映射类型

当使用Nhibernate作为我们的ORM框架时,有四种主要的映射类型:

  1. 基于XML的映射。
  2. 基于特性的映射。
  3. Fluent映射。
  4. 基于约定的映射,有时也称为自动映射。

在接下来的三篇文章里,将详细讲解除“基于特性映射”以外的映射类型。

Fluent映射

本篇文章的所有操作需要在NHibernate初学者指南(3):创建Model中创建的代码基础上完成,代码下载地址:点击这里下载。

为了能够使用Fluent方式映射我们的模型到底层的数据库,需要添加Fluent NHibernate和NHibernate两个引用,这两个程序集可以在NHibernate初学者指南(2):一个完整的例子中创建的lib文件中找到。

引用程序集

映射类

要映射实体到数据库表,必须往我们的项目里添加一个继承自ClassMap<T>(定义在FluentNHibernate.dll程序集中)的类。例如映射Product实体,我们应该定义一个如下面代码的映射类:

public class ProductMap : ClassMap<Product>
{
    public ProductMap()
    {
        // 这里定义映射信息
    }
}

映射信息必须定义在映射类的默认构造函数中。注意ClassMap<T>要求映射的类作为泛型参数,本例中是Product类。

实体级别设置

如果想让数据库理解领域模型,我们需要指定最起码的详细信息。然而,为了额外的灵活性,我们有能力明确指定更多的详细信息。作为一个例子,如果映射到的表名称不同于实体的类名称,我们可以定义表的名称。可以使用下面的代码定义:

public ProductMap()
{
    // 这里定义映射信息
    Table("tbl_Product");
}

如果一个特定的表驻留在另一个架构(schema)中,我们还可以使用下面的代码指定相应的架构:

public ProductMap()
{
    // 这里定义映射信息
    //Table("tbl_Product");
    Schema("OrderingSystem");
}

默认情况下,Fluent NHibernate配置实体为延迟加载(lazy loaded)。如果不喜欢这样,可以使用下面的代码改变默认的行为:

public ProductMap()
{
    // 这里定义映射信息
    //Table("tbl_Product");
    //Schema("OrderingSystem");
    Not.LazyLoad();
}

还有更多的信息可以指定,但不在本次介绍的范围之内。

ID列

通常我们首先要映射的是实体的ID。这需要使用Id方法完成。这个方法有很多选项,使用最简单的形式,如下面的代码:

public ProductMap()
{
    Id(x => x.ID).GeneratedBy.HiLo("1000");
}

使用上面的代码,我们就告诉了NHibernate,Product实体的Id属性应该作为主键映射,新的ID会由NHibernate的HiLo生成器自动生成。只要数据库里我们的字段为Id,一切处理正常。然而,如果命名为ID或PRODUCT_ID呢?答案是根本就不会处理了。这种情况下,必须添加可选的列参数指定列的名称:

Id(x => x.ID, "PRODUCT_ID").GeneratedBy.HiLo("1000");

另外,还可以写成下面的形式:

Id(x => x.ID).Column("PRODUCT_ID").GeneratedBy.HiLo("1000");

ID还有一个经常使用的可选属性:UnsavedValue。它指定一个新对象在持久化到数据库之前应该返回什么值。如下面的代码:

Id(x => x.ID, "PRODUCT_ID").GeneratedBy.HiLo("1000").UnsavedValue(-1);

注意我们没有显示的指定ID的类型。Fluent NHibernate可以通过反射获得它的类型。总之,我们应该避免指定冗余的信息,因为没有必要,只会使你的代码杂乱。

属性

映射简单的值属性,我们使用Map方法。简单值属性是含有基本.NET类型之一的属性,如int,float,double,decimal,bool,string或DateTime。这样的属性简单的映射到数据库列。例如,Product实体的Name属性,使用最简单的形式,如下面的代码所示:

Map(x => x.Name);

上面的代码告诉NHibernate映射Name属性到数据库中有相同名字的列。SQL Server中数据库列的类型是nvarchar,数据库列的最大长度是255。此外,列是可空的。

如果列的最大长度为50,不为空,则如下面代码所示:

Map(x => x.Name).Length(50).Not.Nullable();

如果数据库列的名字和属性的名字不同,可以使用下面的代码指定,跟Id是一样的:

Map(x => x.Name, "PRODUCT_NAME").Length(50).Not.Nullable();

有时,NHibernate不能自动的理解我们如何映射某个属性,必须指定我们的意图。作为例子,我们想映射一个枚举类型的属性。假设实体Product有一个ProductTypes类型的属性ProducType,ProductTypes是一个枚举,如下:

public enum ProductTypes
{
    ProductTypeA,
    ProductTypeB
}

然后我们可以添加一个CustomType方法映射这个属性,如下:

Map(x => x.ProductType).CustomType<ProductTypes>();

在这种情况下,因为enum背后的类型是整型(默认是int),对SQL Server来说数据库字段会是int类型的。NHibernate会自动的在领域模型的enum值和数据库中的int类型的数字之间转换。

另一个经常使用的是将bool类型的属性映射为数据库中char(1)类型的列,true就是'Y',false是'N'。NHibernate为此定义了一个特殊的映射,称为YesNo。例如人,我们可以使用下面的代码映射Product实体的Discontinued属性:

Map(x => x.Discontinued).CustomType("YesNo");

Fluent NHibernate非常完整,为我们映射简单值属性提供了很大的灵活性。在本节中,只讨论了最重要和最常用的设置。

引用

映射引用另一个实体的属性,如Product实体的Category属性,可以使用下面的代码简单的映射这种关系:

References(x => x.Category);

这种类型的映射表示“多对一”关系,在领域模型中经常使用。

大多数情况下,我们想强制定义这个引用,可以使用下面的代码:

References(x => x.Category).Not.Nullable();

如果我们不显示指定,Product表到Category表的外键名称会是Category_Id,默认约定是属性的名称和ID用下划线组合的。当然,可以使用下面的代码修改外键的名称:

References(x => x.Category).Not.Nullable().ForeignKey("ProductId");

最后一个值得注意的设置是指定引用为唯一的。如下面的代码:

References(x => x.Category).Not.Nullable().Unique();

集合

现在说说一对多关系。让我们看一个订单包含line item对象的集合的例子。line item集合的映射如下面的代码:

public OrderMap()
{
    HasMany(x => x.LineItems);
}

通常定义HasMany映射为Inverse。在order和line items的一对多关系中,如果不标记集合为inverse,那么NHibernate会执行一个额外的UPDATE操作。当标记为Inverse,那么NHibernate会首先持久化拥有集合的实体(order),然后持久化集合中的实体(line items)。避免了额外的UPDATE操作。如下面的代码:

HasMany(x => x.LineItems).Inverse();

还有一个重要的配置就是NHibernate的级联操作。大多数情况下,会使用这个配置,如下面的代码:

HasMany(x => x.LineItems).Inverse().Cascade.AllDeleteOrphan();

映射多对多关系

我们还可以映射多对多关系。在领域模型中,Book实体包含Author子对象的集合,同时,Author实体也包含Book子对象的集合。定义这样的映射代码如下:

HasMany(x =>x.Author); 

如果指定中间表的名称,如下面的代码:

HasMany(x => x.Author).Table("BookAuthor");

映射值对象

NHibernate初学者指南(3):创建Model一篇中,我们了解到值对象是领域模型非常重要的概念,所以,我们需要知道如何映射含有值对象作为类型的属性。拿Customer实体的Name属性作为例子。首先,我们可能想映射值对象自身。我们定义一个NameMap类,它继承自ComponentMap<Name>,如下面代码所示:

public class NameMap : ComponentMap<Name>
{
    ...}

然后,在这个类的默认构造函数中添加映射的详细信息,如下面代码所示:

public NameMap()
{
    Map(x => x.LastName).Not.Nullable().Length(50);
    Map(x => x.MiddleName).Length(50);
    Map(x => x.FirstName).Not.Nullable().Length(50);
}

一旦映射了值对象,我们就可以定义Customer实体的Name属性的映射。在CustomerMap类的构造函数中添加如下代码:

Component(x => x.CustomerName);

讲完了Fluent映射所需的基本知识,下面完成我们模型的映射。

实战时间–映射我们的Model

1. 按照本篇文章开始的要求,下载源码,添加引用。

2. 在NHibernate初学者指南(3):创建Model中我们使用了GUID类型的ID,这一篇中,我们想使用int类型的ID,打开Entity<T>类,修改属性ID为:

public int ID { get; private set; }

3. 将Entity<T>基类中的Guid.Empty使用0(零)替换。

4. 在项目中新建一个文件夹Mappings。

5. 映射Employee实体,新建一个EmployeeMap类,继承自ClassMap<Employee>,代码如下:

public class EmployeeMap : ClassMap<Employee>
{
    public EmployeeMap()
    {
        Id(x => x.ID).GeneratedBy.HiLo("100");
        Component(x => x.Name);
    }
}

6. 映射Name值对象,新建NameMap类,继承自ComponentMap<Name>,代码如下:

public class NameMap : ComponentMap<Name>
{
    public NameMap()
    {
        Map(x => x.LastName).Not.Nullable().Length(100);
        Map(x => x.MiddleName).Length(100);
        Map(x => x.FirstName).Not.Nullable().Length(100);
    }
}

7. 映射Customer实体,新建一个CustomerMap类,继承自ClassMap<Customer>,代码如下:

public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Id(x => x.ID).GeneratedBy.HiLo("100");
        Component(x => x.CustomerName);
        Map(x => x.CustomerIdentifier).Not.Nullable().Length(50);
        Component(x => x.Address);
        HasMany(x => x.Orders).Access.CamelCaseField().Inverse().Cascade.AllDeleteOrphan();
    }
}

这里需要解释一下,为了使NHibernate直接访问Customer类的私有字段orders,使用了Access.CamelCaseField()。Cascade.AllDeleteOrphan()告诉NHibernate从customer到它的orders自动的级联所有的插入、更新或删除操作。

8. 映射值对象Address,新建一个AddressMap类,继承自ComponentMap<Address>,代码如下:

public class AddressMap : ComponentMap<Address>
{
    public AddressMap()
    {
        Map(x => x.Line1).Not.Nullable().Length(50);
        Map(x => x.Line2).Length(50);
        Map(x => x.ZipCode).Not.Nullable().Length(10);
        Map(x => x.City).Not.Nullable().Length(50);
        Map(x => x.State).Not.Nullable().Length(50);
    }
}

9. 映射Order实体,新建一个OrderMap类,继承自ClassMap<Order>,代码如下:

public class OrderMap : ClassMap<Order>
{
    public OrderMap()
    {
        Id(x => x.ID).GeneratedBy.HiLo("100");
        Map(x => x.OrderDate).Not.Nullable();
        Map(x => x.OrderTotal).Not.Nullable();
        References(x => x.Customer).Not.Nullable();
        References(x => x.Employee).Not.Nullable();
        HasMany(x => x.LineItems).Inverse().Cascade.AllDeleteOrphan();
    }
}

10. 映射LineItem实体,新建一个LineItem类,继承自ClassMap<LineItem>,代码如下:

public class LineItemMap : ClassMap<LineItem>
{
    public LineItemMap()
    {
        Id(x => x.ID).GeneratedBy.HiLo("100");
        References(x => x.Order).Not.Nullable();
        References(x => x.Product).Not.Nullable();
        Map(x => x.Quantity).Not.Nullable();
        Map(x => x.UnitPrice).Not.Nullable();
        Map(x => x.Discount).Not.Nullable();
    }
}

11. 最后,映射Product实体,新建一个ProductMap类,继承自ClassMap<Product>,代码如下:

public class ProductMap : ClassMap<Product>
{
    public ProductMap()
    {
        Id(x => x.ID).GeneratedBy.HiLo("100");
        Map(x => x.Name).Not.Nullable().Length(50);
        Map(x => x.Description).Length(4000);
        Map(x => x.UnitPrice).Not.Nullable();
        Map(x => x.ReorderLevel).Not.Nullable();
        Map(x => x.Discontinued);
    }
}

至此,我们完成了模型的映射。现在NHibernate可以理解如何在数据库中组织来自domain的数据了。

posted @ 2011-11-14 17:36  BobTian  阅读(4620)  评论(3编辑  收藏  举报