1. ORM 简介 NHibernate
本文档的 PDF 版本可在此处获取 |HERE|。
本文档的示例可通过 |HERE| 获取。
本文是对 NHibernate 的简要介绍,该框架相当于 Java Hibernate 框架的 .NET 版本。如需完整介绍,可参阅:
书名:《NHibernate实战》,作者:Pierre-Henri Kuaté,出版社:Manning,ISBN:978-1932394924
ORM(对象关系映射器)是一组库,它使利用数据库的程序能够无需发出显式的SQL命令,也无需了解所用SGBD的具体特性,即可对数据库进行操作。
先决条件
在 [débutant-intermédiaire-avancé] 框架下,本文档位于 [intermédiaire] 部分。理解本文需要满足若干先决条件,相关内容可参见我撰写的部分文档:
- C# 2008 语言:[使用 .NET Framework 3.5 学习 C# 3.0(2008)]
- [Spring IoC],可通过网址 [Spring IoC for .NET (2005) ] 获取。 介绍了 Spring.Net [Spring.NET | Homepage ] 框架中控制反转(Inversion of Control)或依赖注入(Dependency Injection)的基础知识。
本文档各段落开头有时会提供阅读建议,这些建议引用了前文的文档。
工具
本案例研究中使用的工具均可免费从网络获取。具体包括(截至2011年12月):
- Nhibernate 3.2,可从网址 [http://nhforge.org/Default.aspx] 获取
- Spring.net 1.3.2 可通过网址 [http://www.springframework.net] 获取。Spring.net 框架功能非常丰富。本文中我们将仅使用其提供的库,以简化 Nhibernate 框架的使用。
- Log4net 1.2.10 可在网址 [http://logging.apache.org/log4net] 获取。该日志框架被 Nhibernate 所采用。
- Nunit 2.5 可通过网址 [http://www.nunit.org/] 获取。该单元测试框架相当于 .NET 平台上的 JUnit 框架,对应于 Java 平台。
- ADO.NET 6.4.4 驱动程序(适用于 SGBD MySQL 5)可在网址 [http://dev.mysql.com/downloads/connector/net] 获取
Visual Studio 2010 项目所需的所有 DLL 文件已汇总到一个 [libnet4] 文件夹中:
![]() |
1.1. NHIBERNATE在分层架构中的位置
一个使用数据库的 .NET 应用程序可以按以下方式进行分层架构设计:
![]() |
[dao] 层通过 API ADO.NET 与 SGBD 进行通信。回顾一下该 API 的主要方法。
在连接模式下,应用程序:
- 与数据源建立连接
- 对数据源进行读写操作
- 关闭连接
以下三个 ADO.NET 接口主要涉及这些操作:
- IDbConnection,该接口封装了连接的属性与方法。
- IDbCommand,该类封装了已执行的 SQL 命令的属性和方法。
- IDataReader,封装了 SQL Select 命令结果的属性和方法。
IDbConnection 接口
用于管理与数据库的连接。该接口的 M 方法和 P 属性包括以下内容:
名称 | 类型 | 作用 |
P | 数据库连接字符串。它指定了与特定数据库建立连接所需的所有参数。 | |
M | 建立与由 ConnectionString 定义的数据库的连接 | |
M | 关闭连接 | |
M | 开始事务。 | |
P | 连接状态:ConnectionState.Closed、 ConnectionState.Open、 ConnectionState.Connecting, ConnectionState.Executing, ConnectionState.Fetching, ConnectionState.Broken |
如果 Connection 是实现接口 IDbConnection 的类,则可以按以下方式建立连接:
接口 IDbCommand
用于执行 SQL 命令或存储过程。该接口的 M 方法和 P 属性包括以下内容:
名称 | 类型 | 作用 |
P | 指定要执行的内容 - 其值取自一个枚举: - CommandType.Text:执行在属性 CommandText 中定义的命令 SQL。这是默认值。 - CommandType.StoredProcedure:执行数据库中的存储过程 | |
P | - 若 CommandType= CommandType.Text,则需执行的 SQL 命令文本 - 若 CommandType= CommandType.StoredProcedure 时需执行的存储过程名称 | |
P | 用于执行命令 SQL 的连接 IDbConnection | |
P | 用于执行命令 SQL 的交易 IDbTransaction | |
P | 已配置的 SQL 命令的参数列表。命令 update articles set price=price*1.1 where id=@id 包含参数 @id。 | |
M | 用于执行命令 SQL Select。将获得一个对象 IDataReader,该对象表示 Select 的结果。 | |
M | 用于执行SQL更新、插入、删除命令。该操作将返回受此操作影响的行数(更新、插入、删除的行数)。 | |
M | 用于执行 SQL 命令,而 Select 仅返回单一结果,例如:select count(*) from articles。 | |
M | 用于创建已配置的 SQL 命令的参数 IDbParameter。 | |
M | 用于优化带参数查询的执行,当该查询使用不同的参数多次执行时。 |
如果 Command 是实现接口 IDbCommand 的类,则不带事务的 SQL 命令的执行形式如下:
接口 IDataReader
用于封装 SQL 和 Select 命令的结果。 一个 IDataReader 对象表示一个包含行和列的表,该表按顺序处理:先处理第一行,然后是第二行,依此类推。该接口的 M 方法和 P 属性包括以下内容:
名称 | 类型 | 角色 |
P | 表的列数IDataReader | |
M | GetName(i) 返回表 IDataReader 中第 i 列的名称。 | |
P | Item[i] 代表表 IDataReader 中当前行的第 i 列。 | |
M | 跳转到表 IDataReader 的下一行。如果读取成功,则返回布尔值 True;否则返回 False。 | |
M | 关闭表 IDataReader。 | |
M | GetBoolean(i):返回表 IDataReader 中当前行第 i 列的布尔值。 其他类似的方法如下:GetDateTime、GetDecimal、GetDouble、 GetFloat、GetInt16、GetInt32、GetInt64、GetString。 | |
M | Getvalue(i):将表 IDataReader 中当前行第 i 列的值作为类型 object 返回。 | |
M | IsDBNull(i) 若表 IDataReader 当前行第 i 列无值(由值 SQL NULL 表示),则返回 True。 |
对 IDataReader 对象的查询通常如下所示:
在之前的架构中,
![]() |
连接器 [ADO.NET] 与 SGBD 相关联。因此,实现接口 [IDbConnection] 的类是:
- 对于 SGBD,是类 [MySQLConnection];对于 MySQL,是类 SQLServer
- [SQLConnection] 类对应 SGBD 和 SQLServer
因此,[dao] 层依赖于所使用的 SGBD。某些框架(Linq、Ibatis.net、 NHibernate)通过在 [dao] 层与所用 SGBD 的 [ADO.NET] 连接器之间添加额外层,消除了这一限制。 在此,我们将使用 [NHibernate] 框架。
![]() |
如上所述,[dao]层不再直接与[ADO.NET]连接器交互,而是与NHibernate框架交互,该框架将向其提供一个与所用[ADO.NET]连接器无关的接口。 这种架构允许在不更改 [dao] 层的情况下更换 SGBD。此时只需更换 [ADO.NET] 连接器即可。
1.2. 示例数据库
为演示如何使用 NHibernate,我们将使用以下 MySQL [dbpam_nhibernate] 数据库:
![]() |
- 在 [1] 中,该数据库包含三个表:
- [employes]:记录托儿所员工的表
- [cotisations]:记录社会保险缴费率的表
- [indemnites]:用于记录员工薪资计算所需信息的表
表 [employes]
![]() |
- [2] 为员工表,[3] 为其字段说明
该表的内容可能如下:
表 [cotisations]
![]() |
- 在 [4] 中,是缴费表;而在 [5] 中,是其字段的含义
该表的内容可能如下:
表 [indemnites]
![]() |
- 在 [6] 中,是津贴表;而在 [7] 中,是其字段的含义
该表的内容可能如下:
将数据库结构导出到文件 SQL 后,结果如下:
请注意,第6、20和36行中,主键ID具有 autoincrement属性。这意味着每次添加记录时,MySQL会自动生成主键值。 开发人员无需为此操心。
1.3. C# 演示项目
为了介绍 NHibernate 的配置和使用,我们将采用以下架构:
![]() |
一个名为 [1] 的控制台程序将通过 [NHibernate] [3] 框架处理来自前一个数据库 [2] 的数据。这将引导我们介绍:
- NHibernate的配置文件
- NHibernate 生成的 API
C# 项目结构如下:
![]() |
该项目所需的组件如下:
- 在 [1] 中,项目所需的 DLL:
- [NHibernate]:框架 NHibernate 中的 DLL
- [MySql.Data]:来自 SGBD MySQL 连接器的 ADO.NET 中的 DLL
- [log4net]:Log4net 框架中的 DLL,用于生成日志
- 在 [2] 中,数据库表的图像类
- 在 [3] 中,配置整个应用程序的文件 [App.config],其中包括 [NHibernate] 框架
- ([4])、测试控制台应用程序
1.3.1. 数据库连接配置
让我们回到测试架构:
![]() |
如上所述,[NHibernate] 必须能够访问数据库。为此,它需要以下信息:
- 管理数据库的 SGBD(MySQL、SQLServer、Postgres、Oracle 等)。 大多数 SGBD 都在 SQL 语言中添加了各自专有的扩展。 通过了解 SGBD,NHibernate 可以将其发出的 SQL 指令适配到该 SGBD 上。 NHibernate 采用了 SQL 的方言概念。
- 数据库连接参数(数据库名称、连接所有者的用户名及其密码)
这些信息可存放在配置文件 [App.config] 中。以下是用于 MySQL 5 数据库的配置:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- 配置部分 -->
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
</configSections>
<!-- 配置NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
<!-- 本节包含 log4net 配置设置 -->
<!-- NOTE IMPORTANTE:日志默认处于非活动状态。 必须通过程序使用以下指令将其启用:log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
<!-- 定义输出附加器(日志的存储位置) -->
<appender name="LogFileAppender" type="log4net.Appender.FileAppender, log4net">
<param name="File" value="log.txt" />
<param name="AppendToFile" value="false" />
<layout type="log4net.Layout.PatternLayout, log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n" />
</layout>
</appender>
<appender name="LogDebugAppender" type="log4net.Appender.DebugAppender, log4net">
<layout type="log4net.Layout.PatternLayout, log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n"/>
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender, log4net">
<layout type="log4net.Layout.PatternLayout, log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n"/>
</layout>
</appender>
<!-- 设置根类别,设定默认优先级,并添加输出器(日志的存储位置) -->
<root>
<priority value="INFO" />
<!--
<appender-ref ref="LogFileAppender" />
<appender-ref ref="LogDebugAppender"/>
-->
<appender-ref ref="ConsoleAppender"/>
</root>
<!-- 为某些特定命名空间指定级别 -->
<!-- 级别可以是:ALL、 DEBUG、INFO、WARN、ERROR、FATAL、OFF -->
<logger name="NHibernate">
<level value="INFO" />
</logger>
</log4net>
</configuration>
- 第 4-7 行:定义了 [App.config] 文件中的配置部分。以第 6 行为例:
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
该行定义了文件 [App.config] 中 NHibernate 的配置部分。它有两个属性:name 和 type。
- [name] 属性用于命名配置部分。该部分必须由 <name>...</name> 标签界定,即第 11-24 行中的 <hibernate-configuration>...</hibernate-configuration>。
- 属性 [type=classe,DLL] 指定了负责处理由属性 [name] 定义的章节的类名称,以及包含该类的 DLL。 在此,该类名为 [NHibernate.Cfg.ConfigurationSectionHandler],位于 DLL 类中,该类又位于 [NHibernate.dll] 类中。需要提醒的是,该 DLL 类属于所研究项目的引用范围。
现在我们来看一下 NHibernate 的配置部分:
<!-- 配置 NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
- 第 2 行:NHibernate 的配置位于 <hibernate-configuration> 标签内。xmlns 属性(Xml NameSpace)确定了用于配置 NHibernate 的版本。 实际上,随着时间的推移,NHibernate的配置方式发生了变化。此处使用的是2.2版本。
- 第3行:NHibernate的配置在此完全包含在<session-factory>标签中(第3行和第14行)。NHibernate会话是用于根据以下模式操作数据库的工具:
- 打开会话
- 通过 API 和 NHibernate 的方法操作数据库
- 关闭会话
该会话由 factory 创建,这是一个泛指能够创建对象的类的通用术语。第 3-14 行配置了该 factory。
- 第4、6、8、9行:配置与目标数据库的连接。主要信息包括所用SGBD的名称、数据库名称、用户名及其密码。
- 第 4 行:定义连接提供程序,即用于请求连接数据库的提供程序。该属性的值为一个 NHibernate 类的名称。该属性与所使用的 SGBD 无关。
- 第 6 行:要使用的驱动程序 ADO.NET。这是针对给定 SGBD(此处为 MySQL)而专门设计的 NHibernate 类的名称。 第 6 行已被注释掉,因为它并非必需。
- 第8行:属性[dialect]将SQL方言设置为与SGBD配合使用。 此处是 SGBD 的方言 MySQL。
如果更换为 SGBD,如何找到其对应的方言 NHibernate? 回到之前的 C# 项目,在 [References] 选项卡中双击 DLL [NHibernate]:
![]() |
- 在 [1] 中,[Explorateur d'objets] 选项卡显示了若干 DLL,其中包括该项目引用的那些。
- 在 [2] 中,DLL [NHibernate]
- 在 [3] 中,包含已开发的 DLL 和 [NHibernate]。其中定义了各种命名空间(namespace)。
- 在 [4] 中,包含命名空间 [NHibernate.Dialect],其中包含定义各种可用方言 SQL 的类。
- 在 [5] 中,包含 SGBD 方言的类 MySQL 5。
![]() |
- 在 [6] 中,[MySqlDataDriver] 类的命名空间在下面第 6 行中使用:
<!-- 配置 NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQLDialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
- 第 9-11 行:数据库连接字符串。该字符串格式为“param1=val1;param2=val2; ...”。通过这样定义的参数集,SGBD 驱动程序即可建立连接。 该连接字符串的格式取决于所使用的 SGBD。主要 SGBD 的连接字符串可在 [http://www.connectionstrings.com/] 网站上找到。 此处,字符串 "Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;" 是 SGBD MySQL 的连接字符串。它表示:
- Server=localhost;:SGBD 与试图建立连接的客户端位于同一台机器上
- Database=dbpam_nhibernate;:目标 MySQL 数据库
- Uid=root;:建立连接的用户为 root
- Pwd=;:该用户没有密码(本示例中的特殊情况)
- 第 12 行:属性 [show_sql] 用于指定 NHibernate 是否应在其日志中记录其向数据库发出的 SQL 命令。 在开发阶段,将此属性设置为 [true] 很有帮助,这样可以准确了解 NHibernate 的具体操作。
- 第 13 行:为理解 <mapping> 标签,让我们回顾应用程序的架构:
![]() |
如果控制台程序是连接器 ADO.NET 的直接客户端,且需要获取员工列表,它将向连接器发出 SQL Select 指令, 并会收到一个类型为 IDataReader 的对象,该程序需处理该对象以获取最初所需的员工列表。
在上文中,控制台程序是 NHibernate 的客户端,而 NHibernate 是连接器 ADO.NET 的客户端。 我们稍后将看到,NHibernate 中的 API 将使控制台程序能够请求员工列表。 NHibernate 将把该请求转换为 SQL Select 命令,并将其交由连接器 ADO.NET 执行。 该连接器将返回一个 IDataReader 类型的对象。基于该对象,Nhibernate 应能构建出所请求的员工列表。这是通过配置实现的。 数据库中的每张表都与一个 C# 类相关联。因此,基于 IDataReader 返回的 [employes] 表中的行,NHibernate 将能够构建一个代表员工的对象列表,并将其返回给控制台程序。 这些表与类之间的映射关系是在配置文件中创建的。NHibernate 使用“mapping”一词来定义这些关系。
让我们回到下面的第 13 行:
<!-- 配置 NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
第 13 行表明,表 <--> 类的配置文件将位于 [pam-nhibernate-demos] 程序集内。程序集是指通过编译项目生成的可执行文件或 DLL。 在此,映射文件将被放置在示例项目的程序集内。要了解该程序集的名称,需查看项目属性:
![]() |
- 在 [1] 中,项目属性
- 在“[Application]”选项卡中,[2],即即将生成的程序集名称[3]。
- 因为输出类型是 [Application console] [4],项目编译生成的文件将命名为 [pam-nhibernate-demos.exe]。 如果输出类型是 [Bibliothèque de classes] [5],那么项目编译生成的文件将命名为 [pam-nhibernate-demos.dll]
- 程序集生成在项目 [6] 的 [bin/Release] 文件夹中。
根据前面的说明,映射表与类之间的映射文件应位于文件 [pam-nhibernate-demos.exe] 和 [6] 中。
1.3.2. 配置映射表 <--> 类的
让我们回到所研究项目的架构:
![]() |
- 在 [1] 中,控制台程序使用 NHibernate 框架中 API 的方法。这两个模块之间交换对象。
- 在 [2] 中,NHibernate 使用了连接器 NET 中的 API。 它向目标 SGBD 发送 SQL 命令。
控制台程序将操作反映数据库表的对象。在此项目中,这些对象及其与数据库表的关联已放置在以下 [Entites] 文件夹中:
![]() |
- 数据库中的每张表都对应一个类,并有一个用于映射两者的文件
表 | 类 | 映射 |
1.3.2.1. 表 [cotisations] 的映射
考虑表 [cotisations]:
![]() |
|
该表中的一行可封装在以下类型的对象中:[Cotisations.cs] :
namespace PamNHibernateDemos {
public class Cotisations {
// 自动属性
public virtual int Id { get; set; }
public virtual int Version { get; set; }
public virtual double CsgRds { get; set; }
public virtual double Csgd { get; set; }
public virtual double Secu { get; set; }
public virtual double Retraite { get; set; }
// 制造商
public Cotisations() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}]", CsgRds, Csgd, Secu, Retraite);
}
}
}
我们为表 [cotisations] 的每一列创建了一个自动属性。 这些属性必须声明为虚拟属性(virtual),因为NHibernate将继承该类并重写(override)其属性。因此,这些属性必须是虚拟的。
请注意,第 1 行显示该类属于命名空间 [PamNHibernateDemos]。
[Cotisations.hbm.xml]映射文件在[cotisations]表与[Cotisations]类之间建立映射,内容如下:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
<class name="Cotisations" table="COTISATIONS">
<id name="Id" column="ID" unsaved-value="0">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="CsgRds" column="CSGRDS"/>
<property name="Csgd" column="CSGD"/>
<property name="Retraite" column="RETRAITE"/>
<property name="Secu" column="SECU"/>
</class>
</hibernate-mapping>
- 映射文件是一个 XML 文件,定义在 <hibernate-mapping> 标签内(第 2 行和第 14 行)
- 第4行:<class>标签用于建立数据库表与类之间的关联。此处,表[COTISATIONS](属性table)与类[Cotisations] (属性 name)。在 .NET 文件中,类必须通过其完整名称(包含命名空间)及其所属的程序集来定义。这两项信息由第 3 行提供。第一项(命名空间)可在类的定义中找到。 第二项(程序集)即为项目的程序集名称。我们之前已说明过如何查找该名称。
- 第 5-7 行:<id> 标签用于定义表 [cotisations] 的主键映射。
- 第 5 行:name 属性指定了 [Cotisations] 类中将接收 [cotisations] 表主键的字段。 属性 column 指定了 [cotisations] 表中用作主键的列。 属性 unsaved-value 用于定义一个尚未生成的主键。该值使 NHibernate 能够知道如何将对象 [Cotisations] 保存到表 [cotisations] 中。 如果该对象的 Id 字段值为 0,则执行 SQL INSERT 操作;否则执行 SQL UPDATE 操作。 unsaved-value的值取决于类[Cotisations]中字段Id的类型。 在此,其类型为 int,而类型 int 的默认值为 0。因此,一个尚未保存的 [Cotisations] 对象(即没有主键)其 Id 字段值为 0。 如果字段 Id 的类型是 Object 或其派生类型,则应写为 unsaved-value=null.
- 第6行:当NHibernate需要保存一个Id=0字段的[Cotisations]对象时,它必须在数据库中执行一项INSERT操作,在此过程中需获取该记录主键的值。 大多数 SGBD 都有专有方法来自动生成该值。<generator> 标签用于定义生成主键时应使用的机制。<generator class="native"> 标签表示应使用所用 SGBD 的默认机制。 我们在第 1.2 节中看到,这三个 MySQL 表的主键都具有 autoincrement 属性。 在执行 INSERT 操作时,NHibernate 不会为新增记录的 ID 列提供值,而是由 MySQL 生成该值。
- 第 8 行:<version> 标签用于定义表中的列(以及与其对应的类字段),该列用于对记录进行“版本控制”。初始时,版本值为 1。每次执行 UPDATE 操作时,该值都会递增。此外, 任何 UPDATE 或 DELETE 操作均需配合以下筛选条件:WHERE ID= id AND VERSION=v1。因此,用户只有在拥有该对象的正确版本时,才能对其进行修改或删除。否则,NHibernate将抛出异常。
- 第 9 行:<property> 标签用于定义普通列映射(既非主键,也非版本列)。 因此,第 9 行表示表 [COTISATIONS] 中的列 CSGRDS 与类 [Cotisations] 的属性 CsgRds 相关联。
1.3.2.2. 表 [indemnites] 的映射
考虑表 [indemnites]:
![]() |
|
该表中的一行可封装在以下类型的对象中:[Indemnites] :
namespace PamNHibernateDemos {
public class Indemnites {
// 自动属性
public virtual int Id { get; set; }
public virtual int Version { get; set; }
public virtual int Indice { get; set; }
public virtual double BaseHeure { get; set; }
public virtual double EntretienJour { get; set; }
public virtual double RepasJour { get; set; }
public virtual double IndemnitesCp { get; set; }
// 构造函数
public Indemnites() {
}
// 身份
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}|{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
}
}
}
映射表文件 [indemnites] <--> 类 [Indemnites] 可能如下所示(Indemnites.hbm.xml):
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
<class name="Indemnites" table="INDEMNITES">
<id name="Id" column="ID" unsaved-value="0">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="Indice" column="INDICE" unique="true"/>
<property name="BaseHeure" column="BASE_HEURE" />
<property name="EntretienJour" column="ENTRETIEN_JOUR" />
<property name="RepasJour" column="REPAS_JOUR" />
<property name="IndemnitesCp" column="INDEMNITES_CP" />
</class>
</hibernate-mapping>
与之前解释的映射文件相比,这里没有什么新内容。唯一的区别在于第 9 行。 attribute unique="true" 表示在表 [indemnites] 中,列 [INDICE] 存在唯一性约束:该列不能存在两行具有相同值的记录。
1.3.2.3. 表 [employes] 的映射
考虑表 [employes]:
![]() |
|
与之前的表相比,新表的特点在于存在外键:[INDEMNITE_ID]列是表[INDEMNITES]中[ID]列的外键。 该字段引用了表 [INDEMNITES] 中用于计算员工津贴的行。
表 [employes] 的类 [Employe] 可能如下所示:
namespace PamNHibernateDemos {
public class Employe {
// 自动属性
public virtual int Id { get; set; }
public virtual int Version { get; set; }
public virtual string SS { get; set; }
public virtual string Nom { get; set; }
public virtual string Prenom { get; set; }
public virtual string Adresse { get; set; }
public virtual string Ville { get; set; }
public virtual string CodePostal { get; set; }
public virtual Indemnites Indemnites { get; set; }
// 构造函数
public Employe() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}
}
}
映射文件 [Employe.hbm.xml] 可能如下所示:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
<class name="Employe" table="EMPLOYES">
<id name="Id" column="ID" unsaved-value="0">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="SS" column="SS"/>
<property name="Nom" column="NOM"/>
<property name="Prenom" column="PRENOM"/>
<property name="Adresse" column="ADRESSE"/>
<property name="Ville" column="VILLE"/>
<property name="CodePostal" column="CP"/>
<many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
</class>
</hibernate-mapping>
新变化出现在第 15 行,新增了一个标签:<many-to-one>。 该标签用于将表 [EMPLOYES] 中的外键列 [INDEMNITE_ID] 映射到类 [Employe] 的属性 [Indemnites]:
namespace PamNHibernateDemos {
public class Employe {
// 自动属性
..
public virtual Indemnites Indemnites { get; set; }
...
}
}
表 [EMPLOYES] 有一个外键 [INDEMNITE_ID],该外键引用了表 [INDEMNITES] 中的列 [ID]。 表 [EMPLOYES] 中的多行(many)可以引用表 [INDEMNITES] 中的同一行(one)。因此该标签命名为 <many-to-one>。此标签在此具有以下属性:
- column:指定表 [EMPLOYES] 中作为表 [INDEMNITES] 外键的列名
- name:指定与该列关联的 [Employe] 类的属性。该属性的类型必须是外键目标表(此处为表 [INDEMNITES])关联的类。已知该类即为前文所述的 [Indemnites] 类。 上文第 5 行即反映了这一点。这意味着当 NHibernate 从数据库中检索 [Employe] 对象时,它也会一并检索与其关联的 [Indemnites] 对象。
- 级联:该属性可取多种值:
- save-update:对对象 [Employe] 执行的插入(save)或更新(update)操作,必须传播到其包含的对象 [Indemnites] 上。
- delete:对对象 [Employe] 的删除操作必须传播到其包含的对象 [Indemnites]。
- all:传播插入(save)、更新(update)和删除(delete)操作。
- none:不传播任何操作
最后,回顾一下文件 [App.config] 中 NHibernate 的配置:
<!-- 配置NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
第 13 行表明,映射文件 *.hbm.xml 将位于 [pam-nhibernate-demos] 程序集内。这并非默认设置,需要在 C# 项目中进行配置:
![]() |
- 在 [1] 中,选择映射文件的属性
- 在 [2] 中,生成操作应设置为 [Ressource incorporée] [3]。这意味着在生成项目时,映射文件将被包含在生成的程序集内。
1.4. API 来自 NHibernate
让我们回到示例项目的架构:
![]() |
在前面的段落中,我们通过两种方式配置了 NHibernate:
- 在 [App.config] 中,我们配置了数据库连接
- 针对数据库中的每张表,我们分别定义了该表的图像类以及映射文件,用于在类与表之间进行转换。
接下来,我们将了解 NHibernate 提供的用于操作数据库数据的方法:插入、更新、删除、查询。
1.4.1. SessionFactory 对象
所有 NHibernate 操作均在会话内进行。典型的 NHibernate 操作序列如下:
- 打开 NHibernate 会话
- 在会话中开始事务
- 在会话中执行持久化操作(Load、Get、Find、CreateQuery、Save、SaveOrUpdate、Delete)
- 提交(commit)或回滚(rollback)事务
- 关闭会话 NHibernate
会话是从类型为 [SessionFactory] 的工厂中获取的。该工厂是由配置文件 [App.config] 中 <session-factory> 标签配置的:
<!-- 配置 NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
在 C# 代码中,可通过以下方式获取 SessionFactory:
ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();
Configuration 类是 NHibernate 框架中的一个类。上述语句利用了 [App.config] 中 NHibernate 的配置部分。由此获得的 [ISessionFactory] 对象包含以下信息:
- 用于建立目标数据库连接的信息
- 数据库表与 NHibernate 处理的持久化类之间的映射文件。
1.4.2. NHibernate 会话
一旦创建了 SessionFactory(此操作仅需执行一次),即可获取用于执行持久化操作的会话 NHibernate。常用代码如下:
try{
// 登录
using (ISession session = sessionFactory.OpenSession())
{
// 开始交易
using (ITransaction transaction = session.BeginTransaction())
{
........................ opérations de persistance
// 事务确认
transaction.Commit();
}
}
}catch (Exception ex){
....
}
- 第 3 行:在 using 子句内基于 SessionFactory 创建会话。 退出 using 子句时,会话将自动关闭。若无 using 子句,则需显式关闭会话(session.Close())。
- 第 6 行:持久化操作将在事务内部进行。要么全部成功,要么全部失败。在 using, 子句内部,事务通过 Commit(第 10 行)进行提交。 如果事务中的某个持久化操作抛出异常,则在退出 using 时,事务将由 Rollback 自动回滚。
- 第 1 行和第 13 行的 try/catch 语句用于拦截 try 块内代码(会话、事务、持久化)可能抛出的异常。
1.4.3. ISession 接口
下面介绍由 NHibernate 会话实现的 ISession 接口中的部分方法:
在会话中启动事务 ITransaction tx=session.BeginTransaction(); | |
清空该会话。其中包含的对象将变为脱离状态。 session.Clear(); | |
关闭会话。会话中包含的对象与数据库同步。此同步操作也会在事务结束时执行。后一种情况最为常见。 session.Close(); | |
创建一个 HQL 查询(Hibernate 查询语言),以便后续执行。 IQuery query=session.createQuery("select e from Employe e); | |
删除一个对象。该对象可能属于当前会话(已关联),也可能不属于(未关联)。在会话与数据库同步时,将对该对象执行 SQL DELETE 操作。 // 加载 BD 中的员工 Employe e = session.Get<Employe>(143); // 删除该员工 session.Delete(e); | |
强制会话与数据库同步。会话内容不会改变。 session.Flush(); | |
从数据库中检索主键为 id 的 T 对象。如果该对象不存在,则将指针设为 null。 // 加载一名员工 BD Employe e = session.Get<Employe>(143); | |
将对象 obj 放入会话中。在调用 Save 之前,该对象没有主键。 在 Save 之后,它才拥有主键。在会话同步时,将在数据库上执行 SQL INSERT 操作。 // 创建一名员工 Employe e = new Employe(){...}; // 保存该员工 e = session.Save(e); | |
如果对象没有主键,则执行操作 Save;如果已有主键,则执行操作 Update。 | |
在数据库中更新对象 obj。随后将在数据库上执行操作 SQL 或 UPDATE。 // 加载一名员工,使用 BD Employe e = session.Get<Employe>(143); // 修改其姓名 e.Nom=...; // 将其更新到数据库中 session.Update(e); |
1.4.4. 接口 IQuery
IQuery 接口用于查询数据库并提取数据。我们已经了解了如何创建该接口的实例:
方法 createQuery 的参数是一个 HQL 查询(Hibernate 查询语言),该语言与 SQL 语言类似,但查询的是类而非表。上述查询请求获取所有员工的列表。 以下是 HQL 查询的一些示例:
select e from Employe e where e.Nom like 'A%'
select e from Employe order by e.Nom asc
select e from Employe e where e.Indemnites.Indice=2
现在我们介绍 IQuery 接口中的部分方法:
将查询结果以 T 对象列表的形式返回 IList<Employe> employes=session.createQuery("select e from Employe e order by e.Nom asc").List<Employe>(); | |
将查询结果以列表形式返回,列表中的每个元素代表 Select 查询的一条结果行,该行以对象数组的形式呈现。 IList 行=session.createQuery("select e.Nom, e.Prenom, e.SS from 员工").List(); lignes[i][j] 表示第 i 行第 j 列的数据,其类型为 object。因此,lignes[10][1] 是一个 object 类型,表示某人的名字。 通常需要进行类型转换,才能以确切的类型获取数据。 | |
返回查询结果中的第一个对象 Employe e=session.createQuery("select e from Employe e where e.Nom='MARTIN'").UniqueResult<Employe>(); |
可以配置一个 HQL 查询:
在第 3 行中的 HQL 查询中,:num 是一个参数,必须在执行查询前为其赋值。 上文中,为此使用了方法 SetString。接口 IQuery 提供了多种 Set 方法,用于为参数赋值:
- - SetBoolean(string name, bool value)
- - SetSingle(string name, single value)
- - SetDouble(字符串 name, 双精度值 value)
- - SetInt32(字符串 name, 整数 value)
- ..
1.5. 一些代码示例
以下示例基于之前研究并如下所示的架构。数据库是同样介绍过的 MySQL [dbpam_nhibernate] 数据库。 这些示例是使用 NHibernate 和 [3] 框架来操作 [2] 数据库的 [1] 控制台程序。
![]() |
接下来示例所基于的 C# 项目即为之前介绍过的:
![]() |
- 在 [1] 中,该项目所需的 DLL:
- [NHibernate]:框架 NHibernate 中的 DLL
- [MySql.Data]:SGBD中ADO.NET连接器的DLL MySQL 5
- [log4net]:用于生成日志的工具的DLL
- 在 [2] 中,数据库表的图像类
- 在 [3] 中,配置整个应用程序的文件 [App.config],其中包括 [NHibernate]
- ([4])、测试控制台应用程序。我们将重点介绍其中部分内容。
1.5.1. 获取数据库内容
程序 [ShowDataBase.cs] 可用于显示数据库内容:
using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;
namespace PamNHibernateDemos
{
public class ShowDataBase
{
private static ISessionFactory sessionFactory = null;
// 主程序
static void Main(string[] args)
{
// 初始化工厂NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// 显示数据库内容
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase1();
}
catch (Exception ex)
{
// 显示异常
Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
}
finally
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
// 等待键盘输入
Console.ReadLine();
}
// 测试1
static void ShowDataBase1()
{
// 登录
using (ISession session = sessionFactory.OpenSession())
{
// 开始事务
using (ITransaction transaction = session.BeginTransaction())
{
// 获取员工列表
IList<Employe> employes = session.CreateQuery(@"select e from Employe e order by e.Nom asc").List<Employe>();
// 显示
Console.WriteLine("--------------- liste des employés");
foreach (Employe e in employes)
{
Console.WriteLine(e);
}
// 获取津贴列表
IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i order by i.Indice asc").List<Indemnites>();
// 显示
Console.WriteLine("--------------- liste des indemnités");
foreach (Indemnites i in indemnites)
{
Console.WriteLine(i);
}
// 检索缴费清单
Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
Console.WriteLine("--------------- tableau des taux de cotisations");
Console.WriteLine(cotisations);
// 提交事务
transaction.Commit();
}
}
}
}
}
说明:
- 第 19 行:创建对象 SessionFactory。正是通过它,我们可以获取 Session 对象。
- 第 24 行:显示数据库内容
- 第 31-37 行:在 try. 的 finally 子句中关闭 SessionFactory
- 第 43 行:显示数据库内容的方法
- 第 46 行:从 SessionFactory 获取 Session。
- 第 49 行:启动事务
- 第52行:执行查询HQL以获取员工列表。由于外键将实体Employe与实体Indemnite关联,因此每个员工都会显示其补偿金。
- 第 60 行:执行查询 HQL 以获取补偿金列表。
- 第68行:执行查询HQL,以获取缴费表中唯一的一行数据。
- 第72行:事务结束
- 第 73 行:结束第 49 行中的 using Itransaction 语句——事务自动关闭
- 第 74 行:结束第 46 行中的 using Isession 语句——会话自动关闭。
显示结果:
请注意第3行和第4行,查询某位员工时,同时也获取了其补偿金。
1.5.2. 向数据库插入数据
程序 [FillDataBase.cs] 用于向数据库插入数据:
using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;
namespace PamNHibernateDemos
{
public class FillDataBase
{
private static ISessionFactory sessionFactory = null;
// 主程序
static void Main(string[] args)
{
// 初始化工厂 NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// 清除数据库内容
Console.WriteLine("Effacement base -------------------------------------");
ClearDataBase1();
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase();
Console.WriteLine("Remplissage base -------------------------------------");
FillDataBase1();
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase();
}
catch (Exception ex)
{
// 显示异常
Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
}
finally
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
// 等待键盘输入
Console.ReadLine();
}
// test1
static void ShowDataBase()
{
// 参见前例
}
// ClearDataBase1
static void ClearDataBase1()
{
// 登录
using (ISession session = sessionFactory.OpenSession())
{
// 开始交易
using (ITransaction transaction = session.BeginTransaction())
{
// 获取员工列表
IList<Employe> employes = session.CreateQuery(@"select e from Employe e").List<Employe>();
// 删除所有员工
Console.WriteLine("--------------- suppression des employés associés");
foreach (Employe e in employes)
{
session.Delete(e);
}
// 获取津贴列表
IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i").List<Indemnites>();
// 删除津贴
Console.WriteLine("--------------- suppression des indemnités");
foreach (Indemnites i in indemnites)
{
session.Delete(i);
}
// 检索缴费清单
Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
Console.WriteLine("--------------- suppression des taux de cotisations");
if (cotisations != null)
{
session.Delete(cotisations);
}
// 提交事务
transaction.Commit();
}
}
}
// FillDataBase
static void FillDataBase1()
{
// 登录
using (ISession session = sessionFactory.OpenSession())
{
// 开始事务
using (ITransaction transaction = session.BeginTransaction())
{
// 创建两项津贴
Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
Indemnites i2 = new Indemnites() { Id = 0, Indice = 2, BaseHeure = 2.1, EntretienJour = 2.1, RepasJour = 3.1, IndemnitesCp = 15 };
// 创建两名员工
Employe e1 = new Employe() { Id = 0, SS = "254104940426058", Nom = "Jouveinal", Prenom = "Marie", Adresse = "5 rue des oiseaux", Ville = "St Corentin", CodePostal = "49203", Indemnites = i1 };
Employe e2 = new Employe() { Id = 0, SS = "260124402111742", Nom = "Laverti", Prenom = "Justine", Adresse = "La Brûlerie", Ville = "St Marcel", CodePostal = "49014", Indemnites = i2 };
// 创建缴费率
Cotisations cotisations = new Cotisations() { Id = 0, CsgRds = 3.49, Csgd = 6.15, Secu = 9.39, Retraite = 7.88 };
// 保存所有数据
session.Save(e1);
session.Save(e2);
session.Save(cotisations);
// 提交事务
transaction.Commit();
}
}
}
}
}
说明
- 第19行:创建了SessionFactory
- 第37-43行:在try程序的finally子句中关闭该程序
- 第 55 行:调用 ClearDataBase1 方法清空数据库。其原理如下:
- 将所有员工(第 64 行)收集到一个列表中
- 逐一删除(第67-70行)
- 第 93 行:方法 FillDataBase1 将部分数据插入数据库
- 创建两个实体 Indemnites(第 102、103 行)
- 创建两名拥有这些津贴的员工(第105、106行)
- 在第 108 行创建一个 Cotisations 对象。
- 第 110、111 行:两个“员工”实体被持久化到数据库中
- 第 112 行:Cotisations 实体也随之被保存
- 人们可能会感到惊讶,为什么第102行和第103行的Indemnités实体没有被持久化。实际上,它们是与Employe实体同时被持久化的。 要理解这一点,我们需要回到实体 Employe 的映射:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
<class name="Employe" table="EMPLOYES">
<id name="Id" column="ID" unsaved-value="0">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="SS" column="SS"/>
<property name="Nom" column="NOM"/>
<property name="Prenom" column="PRENOM"/>
<property name="Adresse" column="ADRESSE"/>
<property name="Ville" column="VILLE"/>
<property name="CodePostal" column="CP"/>
<many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
</class>
</hibernate-mapping>
第15行映射了实体Employe与实体Indemnites之间的外键关系,其cascade属性设置为"save-update",这导致实体Employe中的操作"save "和" update "的操作将传播到内部实体 Indemnites。
显示的屏幕如下:
Effacement base -------------------------------------
--------------- suppression des employés et des indemnités associées
--------------- suppression des indemnités restantes
--------------- suppression des taux de cotisations
Affichage base -------------------------------------
--------------- liste des employés
--------------- liste des indemnités
--------------- tableau des taux de cotisations
Remplissage base -------------------------------------
Affichage base -------------------------------------
--------------- liste des employés
[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[2|2,1|2,1|3,1|15]]
[260124402111742|Laverti|Justine|La Brûlerie|St Marcel|49014|[1|1,93|2|3|12]]
--------------- liste des indemnités
[1|1,93|2|3|12]
[2|2,1|2,1|3,1|15]
--------------- tableau des taux de cotisations
[3,49|6,15|9,39|7,88]
1.5.3. 员工查询
程序 [Program.cs] 包含多种方法,用于演示如何访问和处理数据库中的数据。本文将介绍其中几种。
方法 [FindEmployee] 允许根据社会保险号查找员工:
// FindEmployee
static void FindEmployee() {
try {
// 登录
using (ISession session = sessionFactory.OpenSession()) {
// 开始事务
using (ITransaction transaction = session.BeginTransaction()) {
// 根据员工编号查询员工SS
String numSecu = "254104940426058";
IQuery query = session.CreateQuery(@"select e from Employe e where e.SS=:numSecu");
Employe employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
if (employe != null) {
Console.WriteLine("Employe[" + numSecu + "]=" + employe);
} else {
Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
}
numSecu = "xx";
employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
if (employe != null) {
Console.WriteLine("Employe[" + numSecu + "]=" + employe);
} else {
Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
}
// 提交事务
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
说明
- 第 10 行:执行由 numSecu 配置的 Select 查询
- 第 11 行:为参数 numSecu 赋值,并执行方法 UniqueResult 以获取唯一结果。
生成的屏幕显示:
Recherche d'un employé -------------------------------------
Employe[254104940426058]=[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[2|2,1|2,1|3,1|15]]
Employe[xx] non trouvé...
1.5.4. 插入无效实体
以下方法试图保存未初始化的实体 [Employe]。
// SaveEmptyEmployee
static void SaveEmptyEmployee() {
try {
// 登录
using (ISession session = sessionFactory.OpenSession()) {
// 开始事务
using (ITransaction transaction = session.BeginTransaction()) {
// 创建一个空员工
Employe e = new Employe();
// 创建不存在的津贴
Indemnites i = new Indemnites() { Id = 0, Indice = 3, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
// 将其关联到该员工
e.Indemnites = i;
// 保存员工记录,其余字段留空
session.Save(e);
// 提交事务
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
说明
回顾类 [Employe] 的代码:
namespace PamNHibernateDemos {
public class Employe {
// 自动属性
public virtual int Id { get; set; }
public virtual int Version { get; set; }
public virtual string SS { get; set; }
public virtual string Nom { get; set; }
public virtual string Prenom { get; set; }
public virtual string Adresse { get; set; }
public virtual string Ville { get; set; }
public virtual string CodePostal { get; set; }
public virtual Indemnites Indemnites { get; set; }
// 构造函数
public Employe() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}
}
}
一个未初始化的 [Employe] 对象,其所有字符串类型的字段值均为 null。在将记录插入 [employes] 表时,NHibernate 会将对应这些字段的列留空。 然而在表 [employes] 中,所有列都具有 not null 属性,这不允许列为空。因此驱动程序 ADO.NET 将抛出异常:
1.5.5. 在同一事务中创建两个索引相同的津贴
在表 [indemnites] 中,列 [indice] 被声明为具有 unique 属性,这禁止存在两个具有相同索引的行。以下方法在事务内创建两个具有相同索引的补偿:
// CreateIndemnites1
static void CreateIndemnites1() {
try {
// 登录
using (ISession session = sessionFactory.OpenSession()) {
// 开始交易
using (ITransaction transaction = session.BeginTransaction()) {
// 创建两个具有相同指数的补偿
Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
Indemnites i2 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
// 保存它们
session.Save(i1);
session.Save(i2);
// 提交事务
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
说明
- 第 9 行和第 10 行创建了两个具有相同索引的 Indemnites 实体。然而在数据库中,INDICE 列的属性为 UNIQUE。
- 第12行和第13行将两个实体Indemnites放入持久化上下文中。该上下文将在第15行事务提交时与数据库同步。此同步操作将导致生成两个INSERT。 第二个会因 INDICE 列的唯一性而引发异常。由于处于事务内部,第一个 INSERT 将被回滚。
最终结果如下:
第 9 行可见,表 [indemnites] 为空。未发生任何插入操作。
1.5.6. 在事务外创建两个索引相同的补偿
以下方法在不使用事务的情况下创建两个索引相同的补偿:
// CreateIndemnites2
static void CreateIndemnites2() {
try {
// 登录
using (ISession session = sessionFactory.OpenSession()) {
// 创建两个指数相同的津贴
Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
Indemnites i2 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.94, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
// 保存
session.Save(i1);
session.Save(i2);
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
说明
- 代码与之前相同,但未使用事务。
- 持久化上下文与数据库的同步将在该上下文关闭时进行,即第13行(关闭Session)。该同步操作将触发两个INSERT。 第二个会因 INDICE 列的唯一性而失败。但由于我们不在事务中,第一个 INSERT 不会被回滚。
最终结果如下:
在执行该方法之前,数据库为空。第 6 行显示,表 [indemnites] 包含一行数据。























