Nhibernate cookbook 3.0-翻译
Word文档:Nhibernate CookBook 3.0 翻译
第1章:模型和映射
在本章中,我们将讨论以下主题:
<![if !supportLists]>l <![endif]>映射同一个类使用XML
<![if !supportLists]>l <![endif]>创建类层次结构的映射
<![if !supportLists]>l <![endif]>映射一个one-to-many关系
<![if !supportLists]>l <![endif]>建立基本实体类
<![if !supportLists]>l <![endif]>双向one-to-many的类关系
<![if !supportLists]>l <![endif]>处理版本控制和并发
<![if !supportLists]>l <![endif]>创建映射流利
<![if !supportLists]>l <![endif]>映射conORM
介绍
NHibernate的是一种流行的,成熟的,开放源码的对象/关系映射(ORM)的基于Java的Hibernate项目。对象映射器,例如LINQ to SQL中,实体框架和NHibernate,表,列和键的类和属性的应用程序的对象模型的数据库的关系模型之间的转换。
NHibernate的网页,http://NHForge.org,包含博客,维基,完整的参考文档,以及一个bug跟踪系统。支持可通过非常活跃nhusers谷歌组http://groups.google.com/group/nhusers。
NHibernate的源代码托管在SourceForge上的http://sourceforge.net/projects/nhibernate/。
NHibernate的版本的预编译二进制文件也可在SourceForge上。
使用XML映射类
在任何新的NHibernate应用程序建议的第一步是映射模型。在此第一
例如,我会告诉你如何映射一个简单的产品类。
准备
在我们开始映射之前,让我们得到我们的Visual Studio解决方案设置。请按照下列步骤
建立您的解决方案与NHibernate的二进制文件和模式。
<![if !supportLists]>l <![endif]>从SourceForge在http://sourceforge.net/projects/nhibernate/files/.
<![if !supportLists]>l <![endif]>文件名应该是NHibernate-3.0.0.GA-bin.zip,或许还有一个稍微不同的版本号。
<![if !supportLists]>l <![endif]>在Visual Studio中,创建一个名为Eg.Core一个新的C#类库项目
一个目录名为Cookbook的解决方案。
<![if !supportLists]>l <![endif]>删除Class1.cs文件。
<![if !supportLists]>l <![endif]>在解决方案资源管理器中,在Cookbook解决方案右键单击并选择打开文件夹
在Windows资源管理器。这将打开一个资源管理器窗口的Cookbook目录。
<![if !supportLists]>l <![endif]>里面的Cookbook文件夹中创建名为lib的一个新的文件夹。
<![if !supportLists]>l <![endif]>从NHibernate的3个二进制文件的ZIP解压缩下列文件到lib文件夹:
<![if !supportLists]>u <![endif]>在Required_Bin文件夹中的所有文件
<![if !supportLists]>u <![endif]>在Required_For_LazyLoading\Castle folder文件夹中的所有文件
<![if !supportLists]>l <![endif]>回到Visual Studio中,在解决方案右键单击,并选择添加|新建解决方案文件夹。
将该文件夹命名Schema。
<![if !supportLists]>l <![endif]>在Schema文件夹上右键单击,并选择添加|现有项。
<![if !supportLists]>l <![endif]>浏览到lib文件夹,并添加两个文件: nhibernate-configuration.xsd
和NHibernate - mapping.xsd 。当文件在编辑器中打开,就关闭它们。
<![if !supportLists]>l <![endif]>您的解决方案出现如下图参见下图:
<![if !vml]><![endif]>
怎么办呢......
<![if !supportLists]>1. <![endif]>在Eg.Core,创建一个名为实体用下面的代码一个新的C#类:
using System;
namespace Eg.Core
{
public abstract class Entity
{
public virtual Guid Id { get; protected set; }
}
}
<![if !supportLists]>2. <![endif]>用下面的代码创建一个新的类名为Product:
using System;
namespace Eg.Core
{
public class Product : Entity
{
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual decimal UnitPrice { get; set; }
}
}
<![if !supportLists]>3. <![endif]>建立你的应用程序并更正任何编译错误。
接下来,让我们创建一个NHibernate的映射关系,我们的产品类。请执行下列步骤:
<![if !supportLists]>1. <![endif]>在解决方案资源管理器窗口,在您的项目右键单击,并选择添加|新建项目。
<![if !supportLists]>2. <![endif]>选择左侧窗格中的数据类别。
<![if !supportLists]>3. <![endif]>选择右侧窗格中的XML文件。
<![if !supportLists]>4. <![endif]>将文件命名为Product.hbm.xml。
<![if !supportLists]>5. <![endif]>在解决方案资源管理器中,在Product.hbm.xml单击鼠标右键,然后选择属性。
<![if !supportLists]>6. <![endif]>更改生成操作从内容到嵌入的资源(Embedded Resource)。
<![if !supportLists]>7. <![endif]>在编辑器中,输入Product.hbm.xml下面的XML。让智能感知指导你。
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg.Core"
namespace="Eg.Core">
<class name="Product">
<id name="Id">
<generator class="guid.comb" />
</id>
<property name="Name" not-null="true" />
<property name="Description" />
<property name="UnitPrice" not-null="true" type="Currency" />
</class>
</hibernate-mapping>
它是如何工作的...
在这个方法中,我们首先创建我们的模型。该模型是一个类,将集合被保存或存储在数据库中。持久化类是将要坚持任何类。
实体类是持久化类与一个ID。一个实体类的实例被称为实体。到目前为止,我们的模型只包含了产品的实体类。
我们将这个模型扩展,在接下来的几个方法。
请注意,我们的产品类看起来就像任何其他的普通的旧CLR对象( POCO )类。
其中一个在NHibernate的强烈举行的设计决策是所有实体类应持久性无知,就是他们不应该知道的,或者依赖于NHibernate的。
让我们来看看Id属性有点接近。每个产品实例的ID属性将包含数据库的主键值。在NHibernate中,这个被命名为持久对象标识符( POID )。正如主键值唯一地标识数据库中的行
表中, POID将唯一地标识在内存中的实体。
如果你是NHibernate新手,这种保护的setter方法可能看起来奇怪。
public virtual Guid Id { get; protected set; }
这是一个速记方式来限制访问的ID属性。 Product类之外的代码是无法改变的Id属性的值。然而,NHibernate的设置使用属性高度优化的思考,忽略了保护的限制。这样可以使你的应用程序从不会改变这个值。
接下来,我们创建映射Product实体类。 Visual Studio使用的NHibernate - mapping.xsd架构,以提供IntelliSense,而完成这个映射。作为一般规则,所有的NHibernate映射文件结束与a.hbm.xml扩展,并有建立内嵌资源的作用。 NHibernate通过嵌入的资源搜索在您的程序集,加载每一个与这个扩展。
提示:
【其中一个在映射中最常见的错误是忘记设置生成动作嵌入的资源(Embedded Resource)。这导致了“No Persister for class”映射异常。】
让我们打破这个XML映射。每一个XML映射文件包含一个单一的hibernate-mapping元素。 xmlns属性集的XML命名空间。沿在我们的模式文件夹中的架构,Visual Studio使用此项目启用智能感知里面的NHibernate映射。
该组件属性告诉NHibernate的哪个程序集,默认情况下,包含了我们的类型。同样,namespace属性设置为默认值。在这个映射文件NET命名空间类型。总之,它们使我们能够使用完整的装配合格的简单名称来代替产品Eg.Core.Product,Eg.Core的名字。里面的hibernate-mapping元素,我们有一类元素。 name属性告诉NHibernate这个类元素定义
映射我们的实体类Product。
id元素定义了POID。
name属性是指我们的Id属性产品类。它是区分大小写的,就像在C#语言。
generator元素定义NHibernate将如何产生POIDS。在这种情况下,我们已经告诉
NHibernate的使用guid.comb算法。其他几个选项存在。
property元素对我们的Product类中定义的属性。每个name属性对我们的Product类别匹配的属性的名称。默认情况下,NHibernate的允许空值。加为not-null=”true”告诉NHibernate的不允许空值。
提示:
【 避免重复映射
在一般情况下,最好把你的映射尽可能短,并尽量精简。NHibernate的智能扫描您的模型,并结合这方面的知识与在映射所提供的信息。在大多数情况下,指定在你的映射属性类型仅创建冗余的必须维持。默认的表名称匹配类名,并且每个列名默认情况下,相应的属性相匹配。这是没有必要再指定的。同样,你应该避免设置在映射一个属性时,它匹配的NHibernate的默认值。例如,添加not-null=”false”以你的每一个的性能是多余的,并且使你的映射难以阅读。】
与此对应,在Microsoft SQL Server数据库表用来存储我们的Product实体
出现如下图参见下图。它可能会略有不同,对于其他数据库。
<![if !vml]><![endif]>
更多...
主要有三种方法来开始开发一个NHibernate的应用程序。
<![if !supportLists]>1. <![endif]>model-first方法,在这本书中所采取的路径,我们创建了模型,映射模型,配置NHibernate的,最后从生成我们的数据库中的表
<![if !supportLists]>2. <![endif]>配置优先的方法略有不同。首先,我们建立我们的配置,然后一次添加的每个实体类和映射之一。这是一种更具有迭代接近到模型优先的方法。再次,从数据库生成的模型和映射。
<![if !supportLists]>3. <![endif]>共享一个现有的数据库,当database-first方法只建议与其他应用程序。根据数据库的设计,这通常需要一些先进的映射技术。很多NHibernate的初学者学习下来这条道路的新的数据库应用程序,并最终与映射和建模问题远远超出了他们的经验水平。
映射发生了什么?
在加载时,NHibernate将我们的每一个XML映射的反序列化进入。。图映射对象。 NHibernate的结合这些数据与元数据的实体类创建映射元数据。这个映射元数据包含所有必须的NHibernate了解我们的模型。
索引键和自然ID
一个自然的关键是具有语义或商业价值的一个ID。这“意味着什么”人在真实世界中。索引键是没有任何语义生成的系统ID。这仅仅是一个值,它唯一标识数据库表中的数据。 NHibernate的强烈建议使用索引键的。有两个原因。
首先,利用自然键不可避免地导致使用组合键。组合键是其他对象的自然键组成的多字段的主键。让我们来看看一所大学的课程安排模型。自然键为您的学期或学年的实体可以是
2010年秋季。自然键为生物系可能是BIOL。自然键为入门生物学课程将BIOL 101,该部门的自然键的复合和一个课程号,每个存储在单独的字段,关联的外键。自然键的部分或过程提供将来自术语自然ID的组合,的过程中,和节号。你将有四个不同的部分组成的关键信息。密钥的大小与每个层呈指数增长。这很快导致了令人难以置信的大量的复杂性。
其次,因为自然键有现实世界的意义,它们必须被允许改变随着现实世界的改变。让我们假设你有一个UserName属性的Account类。虽然这可能是唯一的,这不是一个很好的候选人作为一个关键。假设用户名是由姓与名。当有人更改名,你就必须在你的数据库中更新多个外键。相反,如果您使用的是同为POID没有意义的整数,你只需要更新一个单一的用户名字段。
然而,用户名是一个自然的id的最佳候选。一个自然的id是一个属性或设置属性,是独特的,而不是空的。从本质上讲,它是一个实体的自然键,虽然它不被使用作为主键。如图中显示的映射为一个自然的id
下面的代码:
<natural-id mutable="true">
<property name="UserName" not-null="true" />
</natural-id>
在natural-id元素都有一个属性:mutable(可变的)。默认值是false,意味着包含在这个自然的id属性或属性是不可改变的,或常数。在我们的情况下,我们希望让我们的应用程序实时更改帐户的用户名,所以我们设置可变为true。除了一些细微的改进缓存,这种自然的id将创建一个对用户名的唯一数据库索引。
ID生成器的选择
NHibernate提供了许多选项,用于生成POIDS。有些人比别人更好,一般属于以下四类:assigned生成需要应用程序分配一个标识一个对象之前,被持久化。这是典型的,当自然键使用。
非插入POID发生器是用于新的应用的最佳选择。这些生成器允许NHibernate的不写入对象的数据到分配给持久化对象的身份数据库,让NHibernate的延迟写入,直到业务交易完成后,减少往返数据库。下面POID生成器适合在这个分类:
hilo生成使用hi / lo算法,这里的整个范围内的整数整数被保留,并根据需要使用。一旦他们都被使用,另一个范围内被保留。因为身份预订使用数据库管理表,这POID生成器是安全的在一个数据库集群,Web,客户端,或使用服务器应用程序,或在一个单一的数据库是由多个共享其他方案应用程序或应用程序的多个实例。
<![if !supportLists]>l <![endif]>GUID通过调用System.Guid.NewGuid()生成一个GUID 。所有的基于GUID生成器安全地使用在共享的数据库环境。
<![if !supportLists]>l <![endif]>guid.comb结合了10个字节的貌似随机的GUID ,用六个字节表示当前的日期和时间,以形成一个新的GUID。该算法降低了同时保持高性能的索引碎片。
<![if !supportLists]>l <![endif]>guid.native得到一个GUID从数据库中。每一代需要一个往返到数据库中。
<![if !supportLists]>l <![endif]>uuid.hex生成一个GUID ,并将其存储为32位十六进制人类可读的字符串数字带或不带破折号。
<![if !supportLists]>l <![endif]>uuid.string生成一个GUID ,每个转换的GUID的16个字节的二进制相应的字符,并将所产生的16个字符的字符串。这是人类不可读的。
<![if !supportLists]>l <![endif]>counter (也称为VM)是一个简单的递增的整数。它是从初始化系统时钟和计数了。这是不恰当的共享数据库方案。
<![if !supportLists]>l <![endif]>increment(增量)也是一个简单的递增整数。它通过获取初始化从数据库启动时的最大主键值。这是不恰当的共享的数据库方案。
<![if !supportLists]>l <![endif]>sequence(顺序)从支持命名序列的数据库获取一个新的ID ,如Oracle ,DB2和PostgreSQL 。每一代需要往返于数据库。 seqhilo提供更好的性能。
<![if !supportLists]>l <![endif]>seqhilo结合hi / lo算法和顺序,以提供更好的性能在序列生成器。跨越一对一的关系
<![if !supportLists]>l <![endif]>foreign外简单的拷贝键。例如,如果你必须通过一对一的关系,一个外的相关联的联系人和客户发电机客户将复制的ID从匹配的联系人
<![if !supportLists]>l <![endif]>
后插入POID生成器需要将数据持久化到数据库中生成的的ID。这改变的NHibernate的行为非常微妙的方式,并禁止一些性能特点。因此,利用这些POID生成器是强烈反对!他们应该只用于与其他地方的应用程序依赖于这种行为的现有数据库。
<![if !supportLists]>l <![endif]>identity返回一个数据库生成的ID。
<![if !supportLists]>l <![endif]>select执行select在插入后从该行提取的ID。它使用自然id来找到正确的行。
<![if !supportLists]>l <![endif]>sequence-identity返回一个数据库生成的ID为支持数据库命名序列。
<![if !supportLists]>l <![endif]>trigger-identity返回由数据库触发器生成一个ID
最后,将本机产生映射到一个不同的POID生成器,取决于数据库产品。对于Microsoft SQL Server,DB2,Informix,MySQL和PostgreSQL,SQLite和Sybase它相当于身份。对于Oracle和Firebird,,它是相同的序列。在安格尔,它的希洛。
另请参阅
<![if !supportLists]>l <![endif]>创建的类层次结构的映射
<![if !supportLists]>l <![endif]>映射一个one-to-many关系
<![if !supportLists]>l <![endif]>设置基准实体类
<![if !supportLists]>l <![endif]>处理版本控制和并发
<![if !supportLists]>l <![endif]>创建映射流
<![if !supportLists]>l <![endif]>映射与ConfORM
创建类层次结构的映射
这是常见的有子类的继承层次结构。在这个例子中,我会告诉你一个方法映射继承与NHibernate,称为table-per-class结构。
准备
完成前面用XML映射类示例
怎样做?
<![if !supportLists]>1. <![endif]>创建使用下面的代码名为Book的新类:
namespace Eg.Core
{
public class Book : Product
{
public virtual string ISBN { get; set; }
public virtual string Author { get; set; }
}
}
2.创建使用下面的代码名为Movie一个新的类:
namespace Eg.Core
{
public class Movie : Product
{
public virtual string Director { get; set; }
}
}
<![if !supportLists]>2. <![endif]>改变Product映射到下面的代码所示的XML匹配
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg.Core" namespace="Eg.Core">
<class name="Product">
<id name="Id">
<generator class="guid.comb" />
</id>
<discriminator column="ProductType" />
<natural-id mutable="true">
<property name="Name" not-null="true" />
</natural-id>
<property name="Description" />
<property name="UnitPrice" not-null="true" />
</class>
</hibernate-mapping>
<![if !supportLists]>3. <![endif]>创建下面的XML命名Book.hbm.xml一个新的嵌入式资源:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg.Core"
namespace="Eg.Core">
<subclass name="Book" extends="Product">
<property name="Author"/>
<property name="ISBN"/>
</subclass>
</hibernate-mapping>
4.创建另一个名为Movie.hbm.xml嵌入的资源下一个XML:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"assembly="Eg.Core"
namespace="Eg.Core">
<subclass name="Movie" extends="Product">
<property name="Director" />
</subclass>
</hibernate-mapping>
它是如何工作的...
在这个例子中,我们已经映射表每类层次结构,这意味着数据为我们的整个层次结构存储在一个单一的表,如在下面的截图:
<![if !vml]><![endif]>
NHibernate的使用鉴别器列,ProductType(产品型号)在这种情况下,以区分产品,书籍和电影。默认情况下,鉴别包含类名。在这种举例来说,这将是Eg.Core.Product,Eg.Core.Book,或Eg.Core.Movie。这些默认值可以在映射使用一个discriminator-value属性被覆盖在我们的类和子类的元素。
在我们的Book.hbm.xml映射,我们定义了书作为Product与Author子类和ISBN属性。在我们的Movie.hbm.xml映射,我们定义了Movie作为一个Product与Director属性与表和类一一对应结构,我们不能定义任何我们的子类属性not-null =“true”时,因为这会造成在这些领域的一个非空约束。举例来说,如果我们设置了Director属性not-null,我们就不能插入Product或Book实例,因为他们不定义Director属性。如果这是必需的,使用下一个层次结构映射策略之一。
更多
Java可以识别该扩展属性,如延伸是用于Java关键字声明类的继承。 NHibernate的第一次来到生活作为Java的Hibernate的ORM的端口。表每类层次结构是映射的类层次结构的建议的方法,但NHibernate的总是给我们其他的选择。然而,在混合使用这些选项同一个类层次结构是不鼓励,只能在非常有限的情况下,
表类一一对应表中类的映射,基类(Product)的属性存储在一个共享表,而每个子类都有自己的表中为子类的属性。
<![if !vml]><![endif]>
表每个子类使用的连接子类元素,这需要一个关键要素命名的主键列。正如其名称所暗示的,NHibernate将使用一个连接来查询此数据。另外,请注意,我们的产品表中不包含ProductType产品型号列。只表和类一一对应层次结构使用鉴别。使用表和类一一对应,我们的电影映射将出现如下面的代码:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg.Core"
namespace="Eg.Core">
<joined-subclass name="Movie" extends="Product">
<key column="Id" />
<property name="Director" />
</joined-subclass>
</hibernate-mapping>
表中每个具体类
表中每个具体类的映射,每个类都有自己的表包含的列为类和基类的所有属性,如在下面的截图:
<![if !vml]><![endif]>
没有重复的数据。也就是说,从一个Book实例数据只写入书籍表,而不是Product表。获取产品数据,NHibernate将使用联合查询所有三个表。使用表每个具体类,我们的电影映射将出现如下图所示
下面的代码:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Eg.Core"
namespace="Eg.Core">
<union-subclass name="Movie" extends="Product">
<property name="Director" />
</union-subclass>
</hibernate-mapping>
另请参阅
<![if !supportLists]>l <![endif]>映射同一个类使用XML
<![if !supportLists]>l <![endif]>映射一个one-to-many关系
<![if !supportLists]>l <![endif]>设置基实体类
<![if !supportLists]>l <![endif]>处理版本控制和并发
<![if !supportLists]>l <![endif]>创建映射流
<![if !supportLists]>l <![endif]>映射confORM
映射一个one-to-many关系
它通常需要与一个实体到另一个。在这个例子中,我将向您展示如何映射Movies和一个新的实体类,ActorRoles之间有一个一对多的关系。
准备
完成前面创建的类层次结构映射的例子。
怎样做?
<![if !supportLists]>1. <![endif]>创建使用下面的代码名为ActorRole一个新的类:
namespace Eg.Core
{
public class ActorRole : Entity
{
public virtual string Actor { get; set; }
public virtual string Role { get; set; }
}
}
<![if !supportLists]>2. <![endif]>创建ActorRole嵌入的资源映射与下面的XML:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg.Core"
namespace="Eg.Core">
<class name="ActorRole">
<id name="Id">
<generator class="guid.comb" />
</id>
<property name="Actor" not-null="true" />
<property name="Role" not-null="true" />
</class>
</hibernate-mapping>
<![if !supportLists]>3. <![endif]>这个Actors属性添加到Movie类:
using System.Collections.Generic;
namespace Eg.Core
{
public class Movie : Product
{
public virtual string Director { get; set; }
public virtual IList<ActorRole> Actors { get; set; }
}
}
<![if !supportLists]>4. <![endif]>下面的列表元素添加到我们的Movie映射:
<subclass name="Movie" extends="Product">
<property name="Director" />
<list name="Actors" cascade="all-delete-orphan">
<key column="MovieId" />
<index column="ActorIndex" />
<one-to-many class="ActorRole"/>
</list>
</subclass>
它是如何工作的...
我们ActorRole映射很简单。映射同一个类与XML进行更多信息。 ActorRole是不是我们的产品层次结构的一部分。在数据库中,它得到它自己的表,如在下面的截图:
<![if !vml]><![endif]>
正如所料, ActorRole表具有Id, Actor, and Role属性的字段。该MovieId和ActorIndex列来自我们的Actors在Movie列表的映射,不是ActorRole映射。Actor属性使用一个IList集合。
另一个强大的设计与选择NHibernate的,一般一个良好的编程习惯,是自由的使用接口。这使得NHibernate的使用它自己的列表实现,以支持延迟加载,后来在这个配方中讨论。
在我们的Movie映射,Actor属性映射到列表中的元素。联想到一个ActorRole与数据库中的Movie,我们存储电影的ID ,每个ActorRole 。关键的元素告诉NHibernate的存储在这个名为MovieId列。我们定义了作为Actor的列表,这意味着顺序是显著。在主角角色获得最高开票。我们的索引元素定义了ActorIndex列来存储列表中的每个元素的位置。
最后,我们告诉NHibernate的,Actor是一个集合ActorRoles与<one-to-many class="ActorRole" />的。cascade属性的全删除告诉NHibernate的保存相关ActorRole自动对象时,它保存了电影,而当将其删除它会删除一个电影。
更多
有几个项目与此配方讨论
延迟加载的集合
为了提高应用程序的性能,NHibernate支持延迟加载。总之,数据不从数据库加载,直到它被应用程序所需时加载。让我们来看看步骤NHibernate将使用在我们的应用程序从数据库中获取movie:
1.NHibernate的获取Id, Name, Description, UnitPrice, and Director数据从数据库与给定ID的Movie。请注意,我们不加载Actors数据。 NHibernate的使用下面的SQL查询
select
movie0_.Id as Id1_,
movie0_.Name as Name1_,
movie0_.Description as Descript4_1_,
movie0_.UnitPrice as UnitPrice1_,
movie0_.Director as Director1_
from Product movie0_
where
movie0_.ProductType='Eg.Core.Movie' and
movie0_.Id = 'a2c42861-9ff0-4546-85c1-9db700d6175e'
2.NHibernate实例化Movie 对象.
3.NHibernate设置Id, Name, Description, UnitPrice, and Director从数据库中的数据的电影对象的属性。
4.NHibernate的创建一个特殊的延迟加载对象,它实现IList <ActorRole>,并设置Movie对象的Actors属性。它不是一个列出<ActorRoles>,而是一个独立的,NHibernate的特定实现IList <ActorRole>接口。
5.NHibernate的返回Movie对象我们的应用程序。
那么,假设我们的应用程序包含以下代码。请记住,我们并没有加载任何ActorRole数据。
foreach (var actor in movie.Actors)
Console.WriteLine(actor.Actor);
我们第一次枚举集合,延迟加载的对象进行初始化。它加载从具有如图所示的查询数据库中的相关联的ActorRole数据:
SELECT
actors0_.MovieId as MovieId1_,
actors0_.Id as Id1_,
actors0_.ActorIndex as ActorIndex1_,
actors0_.Id as Id0_0_,
actors0_.Actor as Actor0_0_,
actors0_.Role as Role0_0_
FROM ActorRole actors0_
WHERE
actors0_.MovieId= 'a2c42861-9ff0-4546-85c1-9db700d6175e'
我们可以通过添加属性禁用集合的延迟加载lazy="false"我们的映射的列表元素。
延迟加载代理
在其他情况下,NHibernate的还支持延迟加载,通过使用代理对象。假设我们的ActorRole类有一个引用回到Movie,类似下面的代码:
public class ActorRole : Entity
{
public virtual string Actor { get; set; }
public virtual string Role { get; set; }
public virtual Movie Movie { get; set; }
}
如果我们从数据库取一个ActorRole,NHibernate的构建ActorRole对象正如我们所期望的,但它只知道相关Movie.的ID。它不会把所有的必要构建整个电影对象的数据。
相反,它会创建一个代理对象代表Movie.,并启用延迟加载。我们可以,当然,访问此Movie.代理的ID,而无需加载Movie.的数据。如果我们访问代理的任何其他属性或方法,NHibernate将立即获取所有数据为这部Movie。
加载这个数据是完全透明的应用程序。代理对象的行为完全像一个真正的电影公司。这个代理对象是Movie的一个子类。为了子类电影和拦截这些呼叫触发延迟加载,NHibernate的要求从我们的Movie类的几件事情。
<![if !supportLists]>l <![endif]>Movie不能是密封类。
<![if !supportLists]>l <![endif]>Movie必须有一个受保护的或公共的构造函数没有参数。
<![if !supportLists]>l <![endif]>Movie的所有公共成员必须是虚拟的。这包括方法。
NHibernate给了我们这些代理对象创建几个选择。传统的NHibernate代理框架的选择是dynamicproxy,项目的框架的一部分。此外,NHibernate包括LinFu和Spring.NET的支持,并允许你建立你自己。
如果我们指定lazy="false"在类元素Movie的映射,我们可以禁用这种行为。NHibernate不会创建代理的Movie。这将迫使NHibernate立即加载相关的Movie的数据在任何时候装载一个actorrole。加载数据不必要的这样可以快速杀死你的应用程序的性能,并应
用非常具体的,深思熟虑的情况下。
集合
NHibernate支持几个集合类型。最常见的类型如下:
<![if !vml]><![endif]>
所有集合也可以使用ICollection的类型,或者实现自定义集合类型
NHibernate.UserType.IUserCollectionType。只有bag 和 set可用于双向的关系。
Bag
包集合允许重复,意味着顺序并不重要。让我们来谈谈一个包ActorRole实体。包可以包含的role 1, actor role 2, actor role 3, actor role 1, actor role 4, and actor role 1。一个典型的包映射将如下面的代码:
<bag name="Actors">
<key column="MovieId"/>
<one-to-many class="ActorRole"/>
</bag>
相应的actor属性可能是一个IList或ICollection的,甚至一个IEnumerable。有没有办法找出收入囊中明显带有SQL语句的单个条目。例如,有没有办法来构造一个SQL语句删除actor的只是第二个项目角色1从包里。 SQL语句从actor删除其中ActorRoleId='1'将删除所有三个项目。
当一个条目被删除,和更新的包被持久,较老包内容的行被删除,然后包内容重新插入。对于特别大的包包,这可以导致性能问题。为了解决这个问题,NHibernate的还提供了一个idBag凡在包的每个条目是由生成器POID1分配一个ID。这使得NHibernate的唯一解决包条目就像delete from Actors where ActorRoleBagId='2'.映射为一个idBag看起来像下面的代码:
<idBag name="Actors">
<collection-id column="ActorRoleBagId" type="Int64">
<generator class="hilo" />
</collection-id>
<key column="MovieId"/>
<one-to-many class="ActorRole"/>
</idBag>
Lists
List集合也允许重复,但不像包,要求顺序。我们的集合可能包含在索引0,actor role 1 at index 0, actor role 2 at index 1, actor role 3 at index 2, actor role 1 at index 3, actor role 4 at index 4, and actor role 1 at index 5.一个典型的映射表看起来像
下面的代码:
<list name="Actors">
<key column="MovieId" />
<list-index column="ActorRoleIndex" />
<one-to-many class="ActorRole"/>
</list>
相应的actor属性应该是一个IList。因为NHibernate的维护为了与ActorRoleIndex顺序,它也可以唯一地识别个体的列表条目。然而,因为它维持顺序,这也意味着这些索引都必须重新设置每当列表内容改变。例如,假设我们有六个角色的演员名单,并我们删除了第三个演员的角色。 NHibernate的更新每个列表项的ActorRoleIndex。
Sets
Sets集合不允许重复,以及一组的顺序并不重要。在我应用程序,这是最常见的集合类型。一组可能包含的actor role 1, actorrole 3, actor role 2, and actor role 4.以actor role 1再次添加到该集合的尝试将会失败。一个典型的集映射将如下面的代码:
<set name="Actors">
<key column="MovieId" />
<one-to-many class="ActorRole"/>
</set>
相应的actor属性应该是一个ISet从Iesi.Collections.dll文件。目前,NHibernate不直接支持ISET接口在.NET Framework4。将项目添加到一个未初始化的延迟加载集合集合的尝试都将导致该组是从数据库加载。这是必要的,以确保集合中唯一性。为了确保在一组适当的独特性,你应该重写Equals和GetHashCode方法,如在接下来的配方所示。
Map
Map是越过NHibernate的时候是从Java移植的另一个术语.NET中,它被称为字典。每一个集合项是一个键或值对。密钥必须独一无二的。值可能不是唯一的。
<map name="Actors" >
<key column="MovieId" />
<map-key column="Role" type="string" />
<element column="Actor" type="string"/>
</map>
正如你可能已经猜到了,相应的Actors属性必须是一个的IDictionary<string, String>字符串,这里的关键是Actor’s名称,并且该值是Actor的名字。你是不是仅限于基本数据类型,如下所示。NHibernate的也可以如下面的代码键和值的实体:
<map name="SomeProperty">
<key column="Id" />
<index-many-to-many class="KeyEntity"/>
<many-to-many class="ValueEntity" />
</map>
另请参阅
<![if !supportLists]>l <![endif]>映射同一个类使用XML
<![if !supportLists]>l <![endif]>映射一个one-to-many关系
<![if !supportLists]>l <![endif]>设置基实体类
<![if !supportLists]>l <![endif]>处理版本控制和并发
<![if !supportLists]>l <![endif]>创建映射流
<![if !supportLists]>l <![endif]>映射confORM
建立基本实体类
我会告诉你如何设置一个基类来使用您的实体。
准备
完成前面三个。
它是如何工作的...
我们ActorRole映射很简单。映射同一个类与XML进行更多信息。 ActorRole是不是我们的产品层次结构的一部分。在数据库中,它得到它自己的表,如在下面的截图:
<![if !supportLists]>1. <![endif]>在Entity.cs,使用下面的代码为实体类:
public abstract class Entity<TId>
{
public virtual TId Id { get; protected set; }
public override bool Equals(object obj)
{
return Equals(obj as Entity<TId>);
}
private static bool IsTransient(Entity<TId> obj)
{
return obj != null && Equals(obj.Id, default(TId));
}
private Type GetUnproxiedType()
{
return GetType();
}
public virtual bool Equals(Entity<TId> other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (!IsTransient(this) && !IsTransient(other) && Equals(Id, other.Id))
{
var otherType = other.GetUnproxiedType();
var thisType = GetUnproxiedType();
return thisType.IsAssignableFrom(otherType) || otherType.IsAssignableFrom(thisType);
}
return false;
}
public override int GetHashCode()
{
if (Equals(Id, default(TId)))
return base.GetHashCode();
return Id.GetHashCode();
}
}
<![if !supportLists]>2. <![endif]>到同一个文件中,添加一个额外的实体类,如下面的代码所示:
public abstract class Entity : Entity<Guid>
{
}
它是如何工作的...
NHibernate的依赖于Equals方法来确定的相等。定义默认行为在System.Object的使用引用相等对于引用类型,包括类。即,x.Equals ( y)是仅当x和y指向同一个对象实例,则为true 。
此默认工作很好以及在大多数情况下。为了支持延迟加载,使用NHibernate的代理对象。我们得到的教训在前面的配方,这些代理对象是真正的实体类的子类,与每一个成员重写以
启用延迟加载。代理对象和默认的组合等于行为可能导致微妙和意想不到的错误在你的应用程序。应用程序不应该知道代理对象,并因此,可以预料,一个代理和一个真正的实例,表示同一个实体将平等的。与ID=8产品实例应该等于一个不同的产品实例或与ID=8产品代理。
为了解决这个问题,我们必须覆盖默认的Equals的行为。我们的实体基类,我们重写Equals方法来确定基于平等上POID.中的Equals ( Object obj)在,我们只需调用的Equals (Entity<TId>other),尝试将对象转换为实体。如果不能转换,传递null代替。
如果其他为null ,对象是不相等的。这有两个目的。首先,x.Equals (null)应该总是返回false 。其次, someEntity. Equals(notAnEntity)也应该返回false 。接下来,我们比较的参考。显然,如果两个变量引用相同的实例,它们是相等的。如果ReferenceEquals(this,other) returns true, ,我们返回true 。
下面,我们比较IDS为默认值,以确定该实体是短暂的。transient object (事务对象)是没有被持久化到数据库中的对象。默认情况下( TID )返回无论默认可能是TID 。为的GUID ,默认是Guid.Empty 。为字符串和所有其他引用类型,它是null.。对于数值类型,它是0。如果Id属性等于默认值,该实体是短暂的。如果一个或两个实体是短暂的,我们给并返回false 。
如果两个实体都坚持着,他们都有POIDS 。我们可以将这些POIDS比较确定平等。如果POIDS不匹配,我们肯定知道这两个实体都没有平等的。我们返回false 。
最后,我们还有最后一个检查。我们知道,两个实体是持久的,并且他们有相同的ID。这并不完全证明他们是平等的。这是完全合法的一个ActorRole实体具有相同的POID作为产品的实体。我们最后的检查是比较类型。如果一类是分配给另一种类型,那么我们肯定知道这两个是相等的。
假设other是产品的代表书实体的代理,而这是一个实际的Book实例代表相同的entity. this.Equals(other)应该return true,因为它们都表示相同的实体。不幸的是, other.GetType ( )将返回类型ProductProxy12398712938代替的类型产品。如ttypeof(ProductProxy12398712938).IsAssignableFrom(typeof(Book)) returns false,我们的Equals会在这种情况下失败。但是,我们可以用other. GetUnproxiedType()到达下通过代理层,返回的实体类型。因为typeof(Product).IsAssignableFrom(typeof(Book)) returns true我们Equals实现的作品。
因为我们已经被重写equals方法,我们还需要重写GetHashCode满足.NET框架的要求。具体地,如果x.Equals ( y)时,则x.GetHashCode ()和y.GetHashCode ()应该返回相同的值。相反的不一定是真实的,然而, x和y可以共享一个散列码,即使他们不相等的。在我们的实体基础课堂上,我们只需使用Id的哈希码,因为这是我们的相等性检查的基础。
另请参阅
<![if !supportLists]>l <![endif]>映射同一个类使用XML
<![if !supportLists]>l <![endif]>映射一个one-to-many关系
<![if !supportLists]>l <![endif]>设置基实体类
<![if !supportLists]>l <![endif]>处理版本控制和并发
<![if !supportLists]>l <![endif]>创建映射流
<![if !supportLists]>l <![endif]>映射confORM
处理版本控制和并发
对于任何多用户事务处理系统,你必须乐观和悲观之间做出选择并发处理并发更新和版本的问题。在这个食谱,我会告诉你如何正确设置了版本控制和乐观并发使用NHibernate。
准备
完成所有以前的食谱,包括建立一个基础的实体类。
怎么办呢......
<![if !supportLists]>1. <![endif]>在实体基类中,添加一个Version属性,如下面的代码:
public abstract class Entity<TId>
{
public virtual TId Id { get; protected set; }
protected virtual int Version { get; set; }
public override bool Equals(object obj)
{
return Equals(obj as Entity<TId>);
}
2. 在产品映射,添加如下面的代码版本元素:
<natural-id mutable="true">
<property name="Name" not-null="true" />
</natural-id>
<version name="Version" />
<property name="Description" />
<property name="UnitPrice" not-null="true" />
<![if !supportLists]>2. <![endif]>在ActorRole映射,添加如下所示的版本元素:
<id name="Id">
<generator class="guid.comb" />
</id>
<version name="Version" />
<property name="Actor" not-null="true" />
<property name="Role" not-null="true" />
它是如何工作的...
假设你有两个用户的数据库应用程序。用户#1和用户#2的两个获取他们的屏幕上相同的数据,并开始进行更改。用户#1提交她变回到数据库中。几分钟后,用户#2提交他的变化。没有任何并发检查,用户#2的变化会悄悄地覆盖用户#1的变化。有两个可能的如何防止这样的:乐观和悲观并发。
乐观并发是进行数据检查改变之前的任何更新过程执行。在这种情况下,用户#1和用户#2都开始了他们的变化。用户#1她提交变化。当用户#2提交他的变化,他的更新将失败,因为当前数据(后用户#1的变化)不匹配的数据是用户#2的最初从数据库中读取。
在此处所示的例子中,我们使用的版本字段来跟踪更改的实体。
更新语句采用以下形式:
UPDATE Product
SET Version = 2 /* @p0 */,
Name = 'Junk' /* @p1 */,
Description = 'Cool' /* @p2 */,
UnitPrice = 100 /* @p3 */
WHERE Id = '764de11e-1fd0-491e-8158-9db8015f9be5' /* @p4 */
AND Version = 1 /* @p5 */
NHibernate的检查,该版本是相同的值,当实体从加载数据库,然后递增值。如果实体已经更新,版本场不会是1,没有行会被这个声明进行更新。 NHibernate的检测为零受影响的行数,并抛出一个StaleStateException,这意味着实体在内存中是陈旧的,或不与数据库同步。
更多
另一种乐观并发悲观锁定。悲观锁是过程,其中一个用户获得对数据的独占锁,而他们正在编辑它。这需要悲观的看法是,只要有机会,用户#2将覆盖用户#1的变化,所以它的最好不要让用户#2偶看数据。在这种情况下,一旦用户#1获取数据,她有一个排它锁。用户#2将不能读取该数据。他的查询将等待,直到用户#1滴锁定或查询超时。不可避免的是,用户#1将采取电话或步离开了一杯咖啡,而用户#2等待访问数据。要实现这种类型的与NHibernate锁定,您的应用程序必须在一个事务中调用Session.lock。
乐观并发的其他方法
除了整数版本字段,NHibernate可以使用日期时间型版本字段。然而,Micorosoft SQL Server有大约三毫秒。当两个更新几乎同时出现,这可能会失败。它也有可能使用SQL Server 2008中的DATETIME2数据类型,它具有100纳秒,或为版本字段甚至SQL Server的timestamp数据类型。
NHibernate允许你通过使用乐观并发的更传统的形式映射属性optimistic-lock的。一个简单的例子看起来像下面的代码:
<class name="Product"
dynamic-update="true"
optimistic-lock="dirty">
在这种情况下,改变产品名称从东西到垃圾会产生SQL所示
在下面的代码:
UPDATE Product
SET Name = 'Junk' /* @p0 */
WHERE Id = '741bd189-78b5-400c-97bd-9db80159ef79' /* @p1 */
AND Name = 'Stuff' /* @p2 */
这确保了Name值并没有被其他用户更改,因为该用户读取的值。另一个用户可能已经改变了这个实体的其他属性。
另一种方法是乐观锁设置为所有。在这种情况下,产品更新将生成的SQL是这样的:
UPDATE Product
SET Name = 'Junk' /* @p0 */
WHERE Id = 'd3458d6e-fa28-4dcb-9130-9db8015cc5bb' /* @p1 */
AND Name = 'Stuff' /* @p2 */
AND Description = 'Cool' /* @p3 */
AND UnitPrice = 100 /* @p4 */
正如你可能已经猜到了,在这种情况下,我们检查所有属性的值。
当optimistic-lock (乐观)锁设置为脏数据,dynamic-update (动态更新)必须是true。动态更新简单的说就是update语句只更新脏属性或属性更改后的值,而不是显式设置的所有属性。
另请参阅
<![if !supportLists]>l <![endif]>映射同一个类使用XML
<![if !supportLists]>l <![endif]>映射一个one-to-many关系
<![if !supportLists]>l <![endif]>设置基实体类
<![if !supportLists]>l <![endif]>处理版本控制和并发
<![if !supportLists]>l <![endif]>创建映射流
<![if !supportLists]>l <![endif]>映射confORM
创建映射流
该NHibernate项目带来的强类型的C#语法通顺映射NHibernate。在这个食谱,我将向您展示如何使用流利的NHibernate映射我们的Eg.Core模型。
准备
从连贯NHibernate网站下载的连贯NHibernate二进制http://fluentnhibernate.org/downloads。选择一个版本是兼容的与NHibernate的具体构建您使用。的连贯NHibernate也下载包含了必要的组件NHibernate。你不妨用它们来代替。从下载的ZIP文件解压FluentNHibernate.dll到lib文件夹中。完成前面Eg.Core模型和对应的食谱。
怎么办呢......
<![if !supportLists]>1. <![endif]>创建一个名为Eg.FluentMappings一个新的类库项目
<![if !supportLists]>2. <![endif]>添加引用FluentNHibernate.dll.
<![if !supportLists]>3. <![endif]>复制tity.cs,Product.cs,Book.cs,Movie.cs和ActorRole.cs从Eg.Core到新Eg.FluentMappings。
<![if !supportLists]>4. <![endif]>在复制的模式,改由Eg.Core的命名空间到Eg.FluentMappings
<![if !supportLists]>5. <![endif]>在Entity.cs,Version属性从protected 变成 public
<![if !supportLists]>6. <![endif]>添加一个名为Mappings一个新的文件夹
<![if !supportLists]>7. <![endif]>创建使用下面的代码名为ProductMapping一个新的类:
using FluentNHibernate.Mapping;
namespace Eg.FluentMappings.Mappings
{
public class ProductMapping : ClassMap<Product>
{
public ProductMapping()
{
Id(p => p.Id).GeneratedBy.GuidComb();
DiscriminateSubClassesOnColumn("ProductType");
Version(p => p.Version);
NaturalId().Not.ReadOnly().Property(p => p.Name);
Map(p => p.Description);
Map(p => p.UnitPrice).Not.Nullable();
}
}
}
<![if !supportLists]>8. <![endif]>创建使用下面的代码名为BookMapping一个新的类
using FluentNHibernate.Mapping;
namespace Eg.FluentMappings.Mappings
{
public class BookMapping : SubclassMap<Book>
{
public BookMapping()
{
Map(p => p.Author);
Map(p => p.ISBN);
}
}
}
<![if !supportLists]>9. <![endif]>创建使用下面的代码名为MovieMapping一个新的类:
using FluentNHibernate.Mapping;
namespace Eg.FluentMappings.Mappings
{
public class MovieMapping : SubclassMap<Movie>
{
public MovieMapping()
{
Map(m => m.Director);
HasMany(m => m.Actors)
.KeyColumn("MovieId") .AsList(l => l.Column("ActorIndex"));
}
}
}
<![if !supportLists]>10. <![endif]>创建使用下面的代码名为ActorRole一个新的类:
using FluentNHibernate.Mapping;
namespace Eg.FluentMappings.Mappings
{
public class ActorRoleMapping : ClassMap<ActorRole>
{
public ActorRoleMapping()
{
Id(ar => ar.Id) .GeneratedBy.GuidComb();
Version(ar => ar.Version);
Map(ar => ar.Actor) .Not.Nullable();
Map(ar => ar.Role) .Not.Nullable();
}
}
}
它是如何工作的...
连贯NHibernate提供了两种方法进行映射:流利的映射语法和自动映射。在这个配方中,我们使用了流利的映射语法。每个实体类有一个相应的映射类。
因为映射语法要求的类成员可以访问,我们必须改变从保护公众的版本属性。连贯NHibernate还包括一些技巧来解决这个问题。他们完全在wiki在http://wiki. fluentnhibernate.org/Fluent_mapping_private_properties.
映射父类是从ClassMap继承的,并且在一个类的子类继承层次结构从SubclassMap。默认情况下,连贯NHibernate创建一个表每个子类的层次结构。使用一个表每类层次结构来代替,我们指定DiscriminateSubClassesOnColumn的产品。连贯NHibernate不支持表每个具体的类层次结构。
当映射产品的自然ID,我们指定.Not.ReadOnly()。这是相同的如在XML映射设置mutable="true"
属性是使用Map()方法,这相当于该财产映射元素的XML映射。一个一对多集合使用的HasMany()方法,其次是AsMap()映射,AsBag(),AsSet(),或AsList()。 AsList使用COLUMN()方法来指定一个列名称列表索引。
另请参阅
<![if !supportLists]>l <![endif]>映射同一个类使用XML
<![if !supportLists]>l <![endif]>映射一个one-to-many关系
<![if !supportLists]>l <![endif]>设置基实体类
<![if !supportLists]>l <![endif]>处理版本控制和并发
<![if !supportLists]>l <![endif]>创建映射流
<![if !supportLists]>l <![endif]>映射confORM
映射confORM
对符合项目将基于约定的映射NHibernate。在这个配方,我会告诉你如何使用符合约定来映射模型。
准备
1.从谷歌代码Check out源代码
http://code.google.com/p/codeconform/source/checkout。
<![if !supportLists]>3. <![endif]>构建ConfORM项目。
<![if !supportLists]>4. <![endif]>完成前面Eg.Core模型和对应的食谱。
它是如何工作的...
<![if !supportLists]>1. <![endif]>创建一个名为Eg.ConfORMMappings一个新的控制台项目。
<![if !supportLists]>2. <![endif]>添加Eg.Core Model引用,ConfORM.dll和ConfORM.Shop.dll。
<![if !supportLists]>3. <![endif]>在Eg.Core.Entity,使Version属性为Public
<![if !supportLists]>4. <![endif]>在Program.cs中,添加using语句到文件的开头如下:
using System;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;
using ConfOrm;
using ConfOrm.NH;
using ConfOrm.Patterns;
using ConfOrm.Shop.CoolNaming;
using Eg.Core;
using NHibernate;
using NHibernate.Cfg.MappingSchema;
<![if !supportLists]>5. <![endif]>添加以下GetMapping功能到Program类:
private static HbmMapping GetMapping()
{
var orm = new ObjectRelationalMapper();
var mapper = new Mapper(orm,new CoolPatternsAppliersHolder(orm));
orm.TablePerClassHierarchy<Product>();
orm.TablePerClass<ActorRole>();
orm.Patterns.PoidStrategies.Add(new GuidOptimizedPoidPattern());
orm.VersionProperty<Entity>(x => x.Version);
orm.NaturalId<Product>(p => p.Name);
orm.Cascade<Movie, ActorRole>(Cascade.All | Cascade.DeleteOrphans);
mapper.AddPropertyPattern(mi =>mi.GetPropertyOrFieldType() == typeof(Decimal) &&mi.Name.Contains("Price"),
pm => pm.Type(NHibernateUtil.Currency));
mapper.AddPropertyPattern(mi =>orm.IsRootEntity(mi.DeclaringType) &&
!"Description".Equals(mi.Name),pm => pm.NotNullable(true));
mapper.Subclass<Movie>(cm =>cm.List(movie => movie.Actors,colm => colm.Index(
lim => lim.Column("ActorIndex")), m => { }));
var domainClasses = typeof(Entity).Assembly.GetTypes().Where(t => typeof(Entity).IsAssignableFrom(t));
return mapper.CompileMappingFor(domainClasses);
}
<![if !supportLists]>6. <![endif]>添加以下WriteXmlMapping功能:
private static void WriteXmlMapping(HbmMapping hbmMapping)
{
var document = Serialize(hbmMapping);
File.WriteAllText("WholeDomain.hbm.xml", document);
}
<![if !supportLists]>7. <![endif]>添加以下Serialize函数:
private static string Serialize(HbmMapping hbmElement)
{
var setting = new XmlWriterSettings { Indent = true };
var serializer = new XmlSerializer(typeof(HbmMapping));
using (var memStream = new MemoryStream(2048))
using (var xmlWriter = XmlWriter.Create(memStream, setting))
{
serializer.Serialize(xmlWriter, hbmElement);
memStream.Flush();
memStream.Position = 0;
using (var sr = new StreamReader(memStream))
{
return sr.ReadToEnd();
}
}
}
<![if !supportLists]>8. <![endif]>在静态无效的主要方法,添加下面一行:
WriteXmlMapping(GetMapping());
<![if !supportLists]>9. <![endif]>浏览到应用程序的bin\ Debug文件夹,并检查WholeDomain.hbm.xml文件。你应该发现以下熟悉的映射:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
namespace="Eg.Core" assembly="Eg.Core" xmlns="urn:nhibernatemapping-
2.2">
<class name="Product">
<id name="Id" type="Guid">
<generator class="guid.comb" />
</id>
<discriminator />
<natural-id>
<property name="Name" not-null="true" />
</natural-id>
<version name="Version" />
<property name="Description" />
<property name="UnitPrice" type="Currency"not-null="true" />
</class>
<class name="ActorRole">
<id name="Id" type="Guid">
<generator class="guid.comb" />
</id>
<version name="Version" />
<property name="Actor" not-null="true" />
<property name="Role" not-null="true" />
</class>
<subclass name="Book" extends="Product">
<property name="ISBN" />
<property name="Author" />
</subclass>
<subclass name="Movie" extends="Product">
<property name="Director" />
<list name="Actors" cascade="all,delete-orphan">
<key column="MovieId" />
<list-index column="ActorIndex" />
<one-to-many class="ActorRole" />
</list>
</subclass>
</hibernate-mapping>
它是如何工作的..
与标准的NHibernate应用程序,需要NHibernate的每个XML映射和反序列化到一个HbmMapping对象,然后将HbmMapping对象到NHibernate的配置,如图中的下一个图:
<![if !vml]><![endif]>
与ConfORM,我们跳过这一步反序列化。对符合映射器输出从我们的惯例内置HbmMapping对象,准备被添加到配置。
ConfORM符合约定的用途和图案直接从模型建立的映射。在除了符合的默认模式,我们使用在我们的模型中的一些额外的约定。
<![if !supportLists]>1. <![endif]>我们首先指定我们的产品类层次结构,其中包括书籍和电影。我们还单独添加我们的ActorRole实体类。
<![if !supportLists]>2. <![endif]>我们使用GuidOptimizedPoidPattern找到一个名为ID的所有的Guid属性,并将它们映射为POIDS与guid.comb生成器。
<![if !supportLists]>3. <![endif]>从Movie引用到ActorRole,比如我们的演员Actors集合,应该使用cascade="all-delete-orphan"。我们设置了用下面的代码:orm.Cascade<Movie, ActorRole>(Cascade.All| Cascade.DeleteOrphans);
<![if !supportLists]>4. <![endif]>接下来,我们配置了几个约定。所有小数性质的词价格在属性名应映射type="currency"。我们使用下面的代码:
mapper.AddPropertyPattern(mi =>mi.GetPropertyOrFieldType() == typeof(Decimal) &&
mi.Name.Contains("Price"),pm => pm.Type(NHibernateUtil.Currency));
<![if !supportLists]>5. <![endif]>根实体的所有属性,除了那些命名的Description,,映射与not-null="true"。请记住,我们正在使用的表每类层次策略,使我们的子类不应该有not-null的属性。我们使用的代码如下:
mapper.AddPropertyPattern(mi =>orm.IsRootEntity(mi.DeclaringType) &&
!"Description".Equals(mi.Name),pm => pm.NotNullable(true));
<![if !supportLists]>6. <![endif]>最后,我们映射我们的Actors集合中Movies,,在设置列表索引列名到ActorIndex。我们使用下面的代码:
mapper.Subclass<Movie>(cm =>cm.List(movie => movie.Actors,colm => colm.Index(
lim => lim.Column("ActorIndex")), m => { }));
7.在建设我们的HbmMapping对象中的最后一步是调用CompileMappingFor,
通过在每一个实体类型,如下面的代码:
var domainClasses = typeof(Entity).Assembly.GetTypes().Where(t => typeof(Entity).IsAssignableFrom(t));
return mapper.CompileMappingFor(domainClasses);
8.由此产生的映射对象就相当于XML映射包含在WholeDomain.hbm.xml。
另请参阅
<![if !supportLists]>l <![endif]>映射同一个类使用XML
<![if !supportLists]>l <![endif]>映射一个one-to-many关系
<![if !supportLists]>l <![endif]>设置基实体类
<![if !supportLists]>l <![endif]>处理版本控制和并发
<![if !supportLists]>l <![endif]>创建映射流
<![if !supportLists]>l <![endif]>映射confORM
双向one-to-many的类关系
在某些情况下,这是非常有用的实体之间的双向关系。在这个配方,我会告诉你如何设置两个实体类之间的双向one-to-many关系。
怎么办呢...
<![if !supportLists]>1. <![endif]>创建一个名为ManualRelationships一个空的类库项目。
<![if !supportLists]>2. <![endif]>添加Iesi.Collections.dll引用到lib文件夹
<![if !supportLists]>3. <![endif]>添加以下Order类:
public class Order
{
public virtual Guid Id { get; protected set; }
public Order()
{
_items = new HashedSet<OrderItem>();
}
private ISet<OrderItem> _items;
public virtual IEnumerable<OrderItem> Items
{
get
{
return _items;
}
}
public virtual bool AddItem(OrderItem newItem)
{
if (newItem != null && _items.Add(newItem))
{
newItem.SetOrder(this);
return true;
}
return false;
}
public virtual bool RemoveItem(OrderItem itemToRemove)
{
if (itemToRemove != null &&_items.Remove(itemToRemove))
{
itemToRemove.SetOrder(null);
return true;
}
return false;
}
}
<![if !supportLists]>4. <![endif]>添加以下映射名为Order.hbm.xml嵌入的资源:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"assembly="ManualRelationships"
namespace="ManualRelationships">
<class name="Order" table="`Order`">
<id name="Id">
<generator class="guid.comb" />
</id>
<set name="Items"cascade="all-delete-orphan"inverse="true"access="field.camelcase-underscore">
<key column="OrderId" />
<one-to-many class="OrderItem"/>
</set>
</class>
</hibernate-mapping>
<![if !supportLists]>5. <![endif]>添加以下的OrderItem类:
public class OrderItem
{
public virtual Guid Id { get; protected set; }
public virtual Order Order { get; protected set; }
public virtual void SetOrder(Order newOrder)
{
var prevOrder = Order;
if (newOrder == prevOrder)
return;
Order = newOrder;
if (prevOrder != null)
prevOrder.RemoveItem(this);
if (newOrder != null)
newOrder.AddItem(this);
}
}
<![if !supportLists]>6. <![endif]>添加以下映射名为OrderItem.hbm.xml嵌入的资源:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"assembly="ManualRelationships"
namespace="ManualRelationships">
<class name="OrderItem">
<id name="Id">
<generator class="guid.comb" />
</id>
<many-to-one name="Order" column="OrderId" />
</class>
</hibernate-mapping>
它是如何工作的...
对象关系映射(ORM)旨在克服阻抗不匹配在数据库中的应用程序和关系模型对象模型之间。这代表一个双向one-to-many关系不匹配时是特别明显实体之间。在关系模型中,这种双向的关系由一个表示单一的外键。在对象模型中,父实体拥有孩子的集合,每个孩子有一个引用它的父。
要解决这种不匹配,NHibernate忽略的双向关系的一面。在数据库中的外键是基于无论是的OrderItems参考OrderItems的Orders集合,但不能同时使用。我们确定到底的关系,控制使用的收集inverse属性的外键。通过默认情况下,该Order控制的外键。保存OrderItem的新Order将导致以下三种SQL语句:
INSERT INTO "Order" (Id) VALUES (@p0)
INSERT INTO OrderItem (Id) VALUES (@p0)
UPDATE OrderItem SET OrderId = @p0 WHERE Id = @p1
当我们指定inverse =”true”时,该OrderItem的控制外键。这是优选的,因为它消除了额外UPDATE语句,从而导致以下两个SQL语句:
INSERT INTO "Order" (Id) VALUES (@p0)
INSERT INTO OrderItem (OrderId, Id) VALUES (@p0, @p1)
我们是负责保持我们在同步双向关系两侧。在一个正常的课堂上,我们将添加代码中的属性setter或集合的添加或删除方法来自动更新的关系的另一端。 NHibernate然而,抛出
当同时NHibernate的是它初始化一个对象进行操作的异常。
出于这个原因,它是建议我们避免直接操作的两端关系,而是使用专门为此编写方法,因为我们在这里所做的用的AddItem,RemoveItem和SetOrder。请注意,我们已经规划了一组,这意味着这个顺序是不显著,且不允许重复。
更多
从Order映射注意到在我们的表名使用反引号,如下所示
<class name="Order" table="`Order`">
在Microsoft SQL Server,订单是一个关键字。如果我们想使用它作为标识符,表在这种情况下名字,NHibernate将需要把引号。反引号告诉NHibernate的包围与任何字符的标识符可以是相应您使用的数据库。
映射枚举
不正确的映射枚举可能导致不必要的更新。在这个配方,我会告诉你如何枚举属性映射到一个字符串字段。
怎么办呢...
<![if !supportLists]>1. <![endif]>创建一个名为MappingEnums一个新的类库项目。
<![if !supportLists]>2. <![endif]>添加以下AccountTypes枚举:
public enum AccountTypes
{
Consumer,
Business,
Corporate,
NonProfit
}
<![if !supportLists]>3. <![endif]>添加下面的Account类
public class Account
{
public virtual Guid Id { get; set; }
public virtual AccountTypes AcctType { get; set; }
public virtual string Number { get; set; }
public virtual string Name { get; set; }
}
<![if !supportLists]>4. <![endif]>添加NHibernate的映射文件与下面的类映射
<class name="Account">
<id name="Id">
<generator class="guid.comb" />
</id>
<natural-id>
<property name="Number" not-null="true" />
</natural-id>
<property name="Name" not-null="true" />
<property name="AcctType" not-null="true" />
</class>
<![if !supportLists]>5. <![endif]>对AcctType属性元素中,添加一个类型属性以下值:
NHibernate.Type.EnumStringType`1[[MappingEnums.AccountTypes,
MappingEnums]], NHibernate
<![if !supportLists]>6. <![endif]>设置映射为嵌入的资源
它是如何工作的...
默认情况下,NHibernate将枚举映射到基于该数值字段枚举的基础类型,通常是一个int。例如,如果我们设置AcctType到AccountTypes.Corporate的AcctType数据库字段将持有的整数2。
这有一个显著的缺点。本身的整数值不描述业务数据的含义。
一种解决方案是创建一个包含每个枚举值旁边一个一个查找表的描述,但是这必须保持在完美的同步与应用程序代码,因为否则可能会导致严重的版本问题。简单地重新排列的顺序
枚举代码从一个版本到下一个可能会产生灾难性的影响
另一种解决办法,这里所示的,是存储的枚举值的名称在一个字符串字段。例如,如果我们设置AcctType到AccountTypes.Corporate,该AcctType数据库字段将持有的字符串值Corporate.。
通过指定类型属性AcctType,我们告诉NHibernate的使用自定义类。NET类型和数据库之间进行转换。 NHibernate的包括EnumStringType<T>覆盖枚举值转化为数据库以使该字符串名称存储的值,而不是数值。
该类型值NHibernate.Type.EnumStringType`1[[MappingEnums.AccountTypes,MappingEnums],NHibernate的是组装合格名称NHibernate.Type.EnumStringType<AccountType>。
创建类组件
有在那里的一组属性的重复使用情况。这些属性甚至可能有自己的业务逻辑,但他们并不代表一个实体在您的应用程序。他们是值对象。在这个食谱,我会告诉你如何我们可以分开这些属性和业务逻辑到一个组件类而无需创建一个独立的实体。
怎么办呢...
<![if !supportLists]>1. <![endif]>创建一个名为ComponentExamples一个新的类库项目
<![if !supportLists]>2. <![endif]>添加一个Address类具有以下属性:
public virtual string Lines { get; set; }
public virtual string City { get; set; }
public virtual string State { get; set; }
public virtual string ZipCode { get; set; }
<![if !supportLists]>3. <![endif]>添加一个customer具有以下属性
<![if !supportLists]>4. <![endif]>添加以下映射文档
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"assembly="ComponentExamples"
namespace="ComponentExamples">
<class name="Customer">
<id name="Id">
<generator class="guid.comb" />
</id>
<property name="Name" not-null="true" />
<component name="BillingAddress" class="Address">
<property name="Lines" not-null="true" />
<property name="City" not-null="true" />
<property name="State" not-null="true" />
<property name ="ZipCode" not-null="true" />
</component>
<component name="ShippingAddress" class="Address">
<property name="Lines" not-null="true"column="ShippingLines" />
<property name="City" not-null="true"column="ShippingCity" />
<property name="State" not-null="true"column="ShippingState" />
<property name ="ZipCode" not-null="true"column="ShippingZipCode" />
</component>
</class>
</hibernate-mapping>
它是如何工作的...
在这个配方中,我们可以使用Address组件类在我们的模型中没有保持一个独立的实体的开销。我们已经用它在我们的Customer类两种计费和送货地址。生成的数据库表将出现如图所示的下一个屏幕截图
<![if !vml]><![endif]>
我们的模型看起来像这样:
<![if !vml]><![endif]>
我们得到了所有这些都不数据库工作的重用好处。地址字段包括在每个查询的客户,并自动加载。