6. NHibernate ORM 简介
本章简要介绍了 NHibernate,它是 Java Hibernate 框架在 .NET 平台上的对应版本。如需全面了解,请参阅:
书名:《NHibernate实战》,作者:Pierre-Henri Kuaté,出版社:Manning,ISBN-13:978-1932394924
ORM(对象关系映射器)是一组库,它允许使用数据库的程序在不发出显式 SQL 命令且无需了解所用 DBMS 具体细节的情况下对其进行操作。
先决条件
按[初级-中级-高级]的难度等级划分,本文档属于[中级]类别。理解本文需要具备若干先决条件,相关内容可参见我撰写的部分文档:
- C# 2008:《使用 .NET 3.5 框架学习 C# 3.0》
- [Spring IoC],可通过链接 [Spring IoC for .NET] 获取。介绍了 Spring.NET 框架 [Spring.NET | 主页] 中控制反转(IoC)或依赖注入(DI)的基础知识。
本文各段落开头有时会提供阅读建议,这些建议会引用前述文档。
工具
本案例研究中使用的工具可在网上免费获取。具体如下(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/] 获取。该单元测试框架相当于 Java 平台上的 JUnit 框架在 .NET 平台上的对应版本。
- 适用于 MySQL 5 数据库管理系统(DBMS)的 ADO.NET 6.4.4 驱动程序可从 [http://dev.mysql.com/downloads/connector/net] 获取
Visual Studio 2010 项目所需的所有 DLL 已编译到 [libnet4] 文件夹中:
![]() |
6.1. NHIBERNATE 在分层 .NET 架构中的作用
使用数据库的 .NET 应用程序可以按以下方式进行分层结构设计:
![]() |
[DAO] 层通过 ADO.NET API 与 DBMS 进行通信(参见第 3.3 节)。在之前的架构中,[ADO.NET] 连接器与 DBMS 相关联。因此,实现 [IDbConnection] 接口的类是:
- 针对 MySQL 数据库管理系统(DBMS)的 [MySQLConnection] 类
- 用于 SQLServer 数据库管理系统(DBMS)的 [SQLConnection] 类
因此,[DAO]层依赖于所使用的DBMS。某些框架(如Linq、iBatis.net、NHibernate)通过在[DAO]层与所用DBMS的[ADO.NET]连接器之间添加额外层,消除了这一限制。在此,我们将使用[NHibernate]框架。
![]() |
在上图中,[DAO]层不再与[ADO.NET]连接器直接通信,而是通过NHibernate框架进行交互,该框架为其提供了与所用[ADO.NET]连接器无关的接口。这种架构允许您在不修改[DAO]层的情况下切换DBMS,只需更换[ADO.NET]连接器即可。
6.2. 示例数据库
为了演示如何使用 NHibernate,我们将使用第 3.1 节中描述的以下 MySQL 数据库 [dbpam_nhibernate]。将数据库结构导出到 SQL 文件后,结果如下:
请注意,在第 6、20 和 36 行中,主键 ID 的 *<a id="autoincrement"></a> * 属性被设置为 *autoincrement*。这意味着每当添加新记录时,MySQL 会自动生成主键值。开发人员无需为此担心。
6.3. C# 演示项目
为了介绍 NHibernate 的配置和使用,我们将采用以下架构:
![]() |
一个控制台程序 [1] 将通过 [NHibernate] 框架 [3] 操作数据库 [2] 中的数据。这将引导我们介绍:
- NHibernate 配置文件
- NHibernate API
C# 项目结构如下:
![]() |
该项目所需的要素如下:
- 在 [1] 中,项目所需的 DLL 文件:
- [NHibernate]:NHibernate 框架 DLL
- [MySql.Data]:MySQL 数据库管理系统 ADO.NET 连接器的 DLL
- [log4net]:用于生成日志的 Log4net 框架 DLL
- 在 [2] 中,表示数据库表的类
- 在 [3] 中,用于配置整个应用程序(包括 [NHibernate] 框架)的 [App.config] 文件
- 在 [4] 中,测试控制台应用程序
6.3.1. 配置数据库连接
让我们回到测试架构:
![]() |
如上所示,[NHibernate] 必须能够访问数据库。为此,它需要以下信息:
- 管理数据库的 DBMS(MySQL、SQL Server、Postgres、Oracle 等)。大多数 DBMS 都为 SQL 语言添加了自身的扩展。通过识别 DBMS,NHibernate 可以将其发出的 SQL 语句适配到该特定的 DBMS 上。NHibernate 采用了 SQL 方言的概念。
- 数据库连接参数(数据库名称、连接所有者的用户名以及密码)
这些信息可以放在 [App.config] 配置文件中。以下是用于 MySQL 5 数据库的配置示例:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- configuration sections -->
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
</configSections>
<!-- configuration 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>
<!-- This section contains the log4net configuration settings -->
<!-- NOTE IMPORTANTE: logs are not active by default. They must be activated programmatically with the log4net.Config.XmlConfigurator.Configure() instruction;
! -->
<log4net>
<!-- Define an output appender (where the logs can go) -->
<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>
<!-- Setup the root category, set the default priority level and add the appender(s) (where the logs will go) -->
<root>
<priority value="INFO" />
<!--
<appender-ref ref="LogFileAppender" />
<appender-ref ref="LogDebugAppender"/>
-->
<appender-ref ref="ConsoleAppender"/>
</root>
<!-- Specify the level for some specific namespaces -->
<!-- Level can be : 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=class,DLL] 属性指定了负责处理由 [name] 属性定义的配置节的类名称,以及包含该类的 DLL。此处,该类名为 [NHibernate.Cfg.ConfigurationSectionHandler],位于 [NHibernate.dll] DLL 中。请注意,该 DLL 是项目引用之一。
现在让我们来看一下 NHibernate 配置部分:
<!-- configuration 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 命名空间)属性指定了用于配置 NHibernate 的版本。随着时间的推移,NHibernate 的配置方式已有所演变。此处使用的是 2.2 版本。
- 第 3 行:整个 NHibernate 配置包含在 <session-factory> 标签内(第 3 行和第 14 行)。NHibernate 会话是根据模式与数据库交互的工具:
- 打开会话
- 使用 NHibernate API 方法操作数据库
- close session
会话由工厂创建,工厂是一个泛指能够创建对象的类的通用术语。第 3–14 行配置了该工厂。
- 第 4、6、8、9 行:配置与目标数据库的连接。主要信息包括所用 DBMS 的名称、数据库名称、用户 ID 以及密码。
- 第 4 行:定义连接提供程序,即用于请求数据库连接的实体。[connection.provider] 属性的值是一个 NHibernate 类的名称。该属性与所使用的 DBMS 无关。
- 第 6 行:要使用的 ADO.NET 驱动程序。这是针对特定 DBMS(此处为 MySQL)的 NHibernate 类名称。第 6 行已被注释掉,因为它并非必需。
- 第 8 行:[dialect] 属性用于设置与该 DBMS 配合使用的 SQL 方言。此处为 MySQL DBMS 方言。
如果更换数据库管理系统,如何查找对应的 NHibernate 方言?请返回之前的 C# 项目,并在 [引用] 选项卡中双击 [NHibernate] DLL:
![]() |
- 在 [1] 中,[对象资源管理器] 选项卡会显示多个 DLL,包括该项目引用的那些。
- 在 [2] 中,[NHibernate] DLL
- 在 [3] 中,是 [NHibernate] DLL。在这里,您可以看到其中定义的各种命名空间。
- 在 [4] 中,[NHibernate.Dialect] 命名空间,其中包含定义各种可用 SQL 方言的类。
- 在 [5] 中,是针对 MySQL 5 数据库管理系统方言的类。
![]() |
- 在 [6] 中,即下方第 6 行所使用的 [MySqlDataDriver] 类的命名空间:
<!-- configuration 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; ..." 的形式。通过这种方式定义的参数集可使 DBMS 驱动程序建立连接。 此连接字符串的格式取决于所使用的数据库管理系统。主要数据库管理系统的连接字符串可在网站 [http://www.connectionstrings.com/] 上找到。此处的字符串 "Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;" 是 MySQL 数据库管理系统的连接字符串。它表示:
- Server=localhost; : 数据库管理系统与尝试建立连接的客户端位于同一台机器上
- 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 返回的 [employees] 表中的行,NHibernate 能够构建一个代表员工的对象列表,并将其返回给控制台程序。这些表到类的映射在配置文件中定义。NHibernate 使用“映射”一词来描述这些关系。
让我们回到下面的第 13 行:
<!-- configuration 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] 处的项目属性
- 在 [应用程序] 选项卡 [2] 中,可查看将生成的程序集名称 [3]。
- 由于输出类型为 [控制台应用程序] [4],项目编译后生成的文件将命名为 [pam-nhibernate-demos.exe]。如果输出类型为 [类库] [5],项目编译后生成的文件将命名为 [pam-nhibernate-demos.dll]
- 该程序集生成在项目的 [bin/Release] 文件夹中 [6]。
根据前面的说明,我们注意到映射表 <--> 类文件必须包含在 [pam-nhibernate-demos.exe] 文件中 [6]。
6.3.2. 配置 <--> 类映射表
让我们回到正在研究的项目架构:
![]() |
- 在[1]中,控制台程序使用了NHibernate框架API的方法。这两个代码块负责对象的交换。
- 在[2]中,NHibernate使用.NET连接器的API。它向目标数据库管理系统(DBMS)发送SQL命令。
该控制台程序将操作反映数据库表的对象。在本项目中,这些对象及其与数据库表的关联已放置在以下 [Entities] 文件夹中:
![]() |
- 每个数据库表都对应一个类以及一个连接两者的映射文件
表 | 表 | 映射 |
contributions | MembershipFees.cs | Contributions.hbm.xml |
员工 | Employee.cs | 员工.hbm.xml |
津贴 | Allowances.cs | Allowances.hbm.xml |
6.3.2.1. [contributions] 表的映射
考虑 [contributions] 表:
![]() |
|
该表中的一行可以如下封装为类型为 [ Cotisations.cs] 的对象:
namespace PamNHibernateDemos {
public class Cotisations {
// automatic properties
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; }
// manufacturers
public Cotisations() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}]", CsgRds, Csgd, Secu, Retraite);
}
}
}
已为 [contributions] 表中的每一列创建了一个自动生成的属性。这些属性必须声明为虚拟属性,因为 NHibernate 会派生该类并重写其属性。因此,这些属性必须是虚拟的。
请注意,第 1 行中,该类属于 [PamNHibernateDemos] 命名空间。
[contributions] 表与 [Cotisations] 类之间的映射文件 [Cotisations.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="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>
- 映射文件是一个定义在 <hibernate-mapping> 标签内的 XML 文件(第 2 行和第 14 行)
- 第 4 行:<class> 标签将数据库表与类建立关联。此处,表名为 [COTISATIONS](table 属性),类名为 [Cotisations](name 属性)。 在 .NET 中,类必须通过其完全限定名(包括命名空间)以及包含该类的程序集来定义。第 3 行提供了这两项信息。第一项(命名空间)可在类定义中找到。第二项(程序集)是项目程序集的名称。我们之前已经解释过如何查找该名称。
- 第 5–7 行:<id> 标签用于定义 [contributions] 表主键的映射。
- 第 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 操作,在此过程中必须获取该记录主键的值。大多数数据库管理系统(DBMS)都有专有的方法来自动生成此值。<generator> 标签用于定义生成主键所采用的机制。 <generator class="native"> 标签表示应采用所用 DBMS 的默认机制。我们在第 6.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 属性相关联。
6.3.2.2. [indemnites] 表的映射
考虑 [indemnites] 表:
![]() |
|
该表中的一行可以如下封装为类型为 [ Indemnites] 的对象:
namespace PamNHibernateDemos {
public class Indemnites {
// automatic properties
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; }
// manufacturers
public Indemnites() {
}
// identity
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] 列存在唯一性约束:[INDICE] 列不能有两行具有相同的值。
6.3.2.3. [employes] 表的映射
现在我们来看 [employes] 表:
![]() |
|
与前面的表不同的是,这里存在一个外键:[INDEMNITE_ID] 列是 [INDEMNITES] 表中 [ID] 列的外键。该字段引用 [INDEMNITES] 表中的行,用于计算员工的津贴。
表示 [employes] 表的 [ Employe] 类可以如下所示:
namespace PamNHibernateDemos {
public class Employe {
// automatic properties
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; }
// manufacturers
public Employe() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}
}
}
映射文件 [Employee.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>。该标签用于将 [EMPLOYEES] 表中的外键列 [INDEMNITE_ID] 映射到 [Employee] 类的 [Benefits] 属性:
namespace PamNHibernateDemos {
public class Employe {
// automatic properties
..
public virtual Indemnites Indemnites { get; set; }
...
}
}
[EMPLOYEES] 表有一个外键 [BENEFIT_ID],它引用了 [BENEFITS] 表中的 [ID] 列。[EMPLOYEES] 表中的多行(多)可以引用 [BENEFITS] 表中的一行(一)。因此,该标签命名为 <many-to-one>。此标签在此处具有以下属性:
- column:指定 [EMPLOYEES] 表中作为 [BENEFITS] 表外键的列名
- name:指定与该列关联的 [Employee] 类的属性。该属性的类型必须是外键目标表对应的类,本例中即 [Compensation] 表。 我们已知该类即为前文所述的 [Indemnites] 类。这在上文第 5 行中有所体现。这意味着当 NHibernate 从数据库中检索 [Employee] 对象时,也会检索相应的 [Indemnites] 对象。
- cascade:此属性可取多种值:
- save-update:对 [Employee] 对象执行的插入(save)或更新操作必须传播到其包含的 [Benefits] 对象。
- delete:删除 [Employee] 对象时,必须将该操作传播至其包含的 [Benefits] 对象。
- all:传播插入(保存)、更新和删除操作。
- none:不传播任何操作
最后,让我们回顾一下 [App.config] 文件中的 NHibernate 配置:
<!-- configuration 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] 中,生成操作必须设置为 [Embedded Resource] [3]。这意味着在生成项目时,映射文件必须嵌入到生成的程序集之中。
6.4. NHibernate API
让我们回到示例项目的架构:
![]() |
在前面的章节中,我们通过两种方式配置了 NHibernate:
- 在 [App.config] 中,我们配置了数据库连接
- 对于数据库中的每张表,我们编写了代表该表的类,以及用于在类与表之间进行转换的映射文件。
我们还需要探索 NHibernate 提供的用于操作数据库数据的方法:插入、更新、删除和查询。
6.4.1. SessionFactory 对象
每项 NHibernate 操作都在一个会话中进行。典型的 NHibernate 操作序列如下:
- 打开一个 NHibernate 会话
- 在会话内启动事务
- 使用会话执行持久化操作(Load、Get、Find、CreateQuery、Save、SaveOrUpdate、Delete)
- 提交或回滚事务
- 关闭 NHibernate 会话
会话是从 [SessionFactory] 工厂获取的。该工厂由 [App.config] 配置文件中的 <session-factory> 标签进行配置:
<!-- configuration 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 管理的持久化类进行映射的文件。
6.4.2. NHibernate 会话
一旦创建了 SessionFactory(此操作仅需执行一次),您就可以获取会话,从而执行 NHibernate 持久化操作。一个常见的代码片段如下:
try{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
........................ opérations de persistance
// transaction validation
transaction.Commit();
}
}
}catch (Exception ex){
....
}
- 第 3 行:在 `using` 代码块内通过 `SessionFactory` 创建了一个会话。当 `using` 代码块结束时,该会话将自动关闭。如果没有 `using` 代码块,则需要显式关闭会话(`session.Close()`)。
- 第 6 行:持久化操作将在事务中执行。要么全部成功,要么全部失败。在 `using` 代码块内,事务通过 `Commit` 进行提交(第 10 行)。如果事务中的持久化操作抛出异常,则在退出 `using` 代码块时,事务将自动回滚。
- 第 1 行和第 13 行的 try/catch 代码块用于捕获 try 代码块内(会话、事务、持久化)抛出的任何异常。
6.4.3. ISession 接口
接下来我们将介绍 NHibernate 会话实现的 ISession 接口中的部分方法:
在会话中启动事务 ITransaction tx = session.BeginTransaction(); | |
清除会话。其中包含的对象将变为脱离状态。 session.Clear(); | |
关闭会话。会话中包含的对象将与数据库进行同步。此同步操作也会在事务结束时执行。后一种情况最为常见。 session.Close(); | |
创建一个 HQL(Hibernate 查询语言)查询,以便稍后执行。 IQuery query = session.createQuery("select e from Employee e"); | |
删除一个对象。该对象可能属于会话(已附加)或不属于会话(已分离)。当会话与数据库同步时,将对该对象执行 SQL DELETE 操作。 // 从数据库中加载一名员工 Employee e = session.Get<Employee>(143); // 删除该员工 session.Delete(e); | |
强制会话与数据库同步。会话内容不会发生改变。 session.Flush(); | |
从数据库中检索主键为 id 的对象 T。如果该对象不存在,则返回空指针。 // 从数据库中加载一名员工 Employee e = session.Get<Employee>(143); | |
将对象 obj 添加到会话中。在调用 Save 方法之前,该对象没有主键。调用 Save 方法后,它便拥有了一个主键。当会话被同步时,将在数据库上执行一条 SQL INSERT 语句。 // 创建一名员工 Employee e = new Employee(){...}; // 保存它 e = session.Save(e); | |
如果 obj 没有主键,则执行保存操作;如果已有主键,则执行更新操作。 | |
更新数据库中的对象 obj。随后将在数据库上执行 SQL UPDATE 操作。 // 从数据库中加载一名员工 Employee e = session.Get<Employee>(143); // 更改其姓名 e.Name = ...; // 更新数据库中的员工 session.Update(e); |
6.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<Employee> employees = session.createQuery("select e from Employee e order by e.Name asc").List<Employee>(); | |
返回查询结果作为列表,其中每个列表项代表 SELECT 查询中的一行,以对象数组的形式呈现。 IList rows = session.createQuery("select e.LastName, e.FirstName, e.SocialSecurity from Employee e").List(); lines[i][j] 表示第 i 行第 j 列的数据,以对象形式呈现。因此,lines[10][1] 是一个表示某人名字的对象。通常需要进行类型转换,才能以确切的类型获取数据。 | |
返回查询结果中的第一个对象 Employee e = session.createQuery("select e from Employee e where e.LastName='MARTIN'").UniqueResult<Employee>(); |
HQL 查询可以进行参数化:
在第 3 行的 HQL 查询中,:num 是一个参数,必须在执行查询之前为其赋值。上文通过 SetString 方法实现了这一目的。IQuery 接口提供了多种 Set 方法,用于为参数赋值:
- - SetBoolean(string name, bool value)
- - SetSingle(string name, single value)
- - SetDouble(string name, double value)
- - SetInt32(string name, int32 value)
- ..
6.5. 一些代码示例
以下示例基于前文讨论并总结于下方的架构。数据库为同样介绍过的 MySQL 数据库 [dbpam_nhibernate]。这些示例是使用 NHibernate 框架 [3] 操作数据库 [2] 的控制台程序 [1]。
![]() |
包含以下示例的 C# 项目即为之前介绍过的那个:
![]() |
- 在 [1] 中,该项目所需的 DLL:
- [NHibernate]:NHibernate 框架 DLL
- [MySql.Data]:用于 MySQL 5 数据库管理系统(DBMS)的 ADO.NET 连接器 DLL
- [log4net]:日志记录工具的 DLL
- 在 [2] 中,表示数据库表的类
- 在 [3] 中,配置整个应用程序(包括 [NHibernate] 框架)的 [App.config] 文件
- 在 [4] 中,是测试控制台应用程序。我们将分部分介绍这些内容。
6.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;
// main program
static void Main(string[] args)
{
// factory initialization NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// database content display
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase1();
}
catch (Exception ex)
{
// exception is displayed
Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
}
finally
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
// keyboard wait
Console.ReadLine();
}
// test1
static void ShowDataBase1()
{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
// retrieve the list of employees
IList<Employe> employes = session.CreateQuery(@"select e from Employe e order by e.Nom asc").List<Employe>();
// we display it
Console.WriteLine("--------------- liste des employés");
foreach (Employe e in employes)
{
Console.WriteLine(e);
}
// retrieve the list of benefits
IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i order by i.Indice asc").List<Indemnites>();
// we display it
Console.WriteLine("--------------- liste des indemnités");
foreach (Indemnites i in indemnites)
{
Console.WriteLine(i);
}
// retrieve the list of contributions
Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
Console.WriteLine("--------------- tableau des taux de cotisations");
Console.WriteLine(cotisations);
// commit transaction
transaction.Commit();
}
}
}
}
}
说明:
- 第 19 行:创建了 SessionFactory 对象。这将使我们能够获取 Session 对象。
- 第 24 行:显示数据库内容
- 第 31–37 行:SessionFactory 在 try 代码块的 finally 子句中被关闭。
- 第 43 行:显示数据库内容的方法
- 第 46 行:我们从 SessionFactory 获取一个 Session。
- 第 49 行:启动事务
- 第 52 行:执行 HQL 查询以检索员工列表。由于外键将 Employee 实体与 Compensation 实体关联,因此每位员工都会有相应的薪酬。
- 第 60 行:用于检索津贴列表的 HQL 查询。
- 第 68 行:HQL 查询,用于从 contributions 表中检索单行数据。
- 第 72 行:事务结束
- 第 73 行:结束第 49 行中的 `using ITransaction` —— 事务自动关闭
- 第 74 行:结束第 46 行开始的 `using Isession` 语句——会话自动关闭。
获得的屏幕显示:
请注意,在第 3 行和第 4 行中,查询员工时,其薪酬信息也会一并返回。
6.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;
// main program
static void Main(string[] args)
{
// factory initialization NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// delete database contents
Console.WriteLine("Effacement base -------------------------------------");
ClearDataBase1();
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase();
Console.WriteLine("Remplissage base -------------------------------------");
FillDataBase1();
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase();
}
catch (Exception ex)
{
// exception is displayed
Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
}
finally
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
// keyboard wait
Console.ReadLine();
}
// test1
static void ShowDataBase()
{
// see previous example
}
// ClearDataBase1
static void ClearDataBase1()
{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
// retrieve the list of employees
IList<Employe> employes = session.CreateQuery(@"select e from Employe e").List<Employe>();
// we cut all employees
Console.WriteLine("--------------- suppression des employés associés");
foreach (Employe e in employes)
{
session.Delete(e);
}
// retrieve the list of allowances
IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i").List<Indemnites>();
// we do away with allowances
Console.WriteLine("--------------- suppression des indemnités");
foreach (Indemnites i in indemnites)
{
session.Delete(i);
}
// retrieve the list of contributions
Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
Console.WriteLine("--------------- suppression des taux de cotisations");
if (cotisations != null)
{
session.Delete(cotisations);
}
// commit transaction
transaction.Commit();
}
}
}
// FillDataBase
static void FillDataBase1()
{
// session opening
using (ISession session = sessionFactory.OpenSession())
{
// start of transaction
using (ITransaction transaction = session.BeginTransaction())
{
// two allowances are created
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 };
// we create two employees
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 };
// we create the contribution rates
Cotisations cotisations = new Cotisations() { Id = 0, CsgRds = 3.49, Csgd = 6.15, Secu = 9.39, Retraite = 7.88 };
// save it all
session.Save(e1);
session.Save(e2);
session.Save(cotisations);
// commit transaction
transaction.Commit();
}
}
}
}
}
说明
- 第 19 行:创建了 SessionFactory
- 第 37–43 行:在 try 代码块的 finally 子句中关闭它
- 第 55 行:调用 ClearDataBase1 方法,该方法用于清空数据库。具体流程如下:
- 将所有员工(第 64 行)检索到一个列表中
- 逐一删除它们(第 67–70 行)
- 第 93 行:FillDataBase1 方法将一些数据插入数据库
- 创建两个 Indemnites 实体(第 102、103 行)
- 使用这些津贴创建两名员工(第 105、106 行)
- 我们在第 108 行创建了一个 Cotisations 对象。
- 第 110、111 行:两个 Employee 实体被持久化到数据库中
- 第 112 行:Cotisations 实体随后被持久化
- 第 102 行和第 103 行中的 Allowances 实体似乎没有被持久化,这可能令人感到意外。实际上,它们是在与 Employee 实体同时被持久化的。要理解这一点,我们需要查看 Employee 实体的映射:
<?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 行映射了 Employee 实体与 Allowances 实体之间的外键关系,其中包含 cascade="save-update" 属性,这意味着对 Employee 实体执行的“保存”和“更新”操作将传播到内部的 Allowances 实体。
获得的屏幕显示:
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]
6.5.3. 搜索员工
程序 [Program.cs] 包含多种方法,演示了如何访问和操作数据库中的数据。本文将介绍其中几种。
[FindEmployee] 方法允许您通过社会保险号查找员工:
// FindEmployee
static void FindEmployee() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// start of transaction
using (ITransaction transaction = session.BeginTransaction()) {
// search for an employee using his SS number
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é...");
}
// commit transaction
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é...
6.5.4. 插入无效实体
以下方法试图保存一个未初始化的 [Employee] 实体。
// SaveEmptyEmployee
static void SaveEmptyEmployee() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// start of transaction
using (ITransaction transaction = session.BeginTransaction()) {
// create an empty employee
Employe e = new Employe();
// we create a non-existent indemnity
Indemnites i = new Indemnites() { Id = 0, Indice = 3, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
// associated with the employee
e.Indemnites = i;
// save the employee, leaving the other fields empty
session.Save(e);
// commit transaction
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
说明
让我们回顾一下 [Employee] 类的代码:
namespace PamNHibernateDemos {
public class Employe {
// automatic properties
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; }
// manufacturers
public Employe() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}
}
}
未初始化的 [Employee] 对象的所有字符串字段都将取空值。在将该记录插入 [employees] 表时,NHibernate 会将这些字段对应的列留空。然而,在 [employees] 表中,所有列都带有 not null 属性,这禁止列为空。此时,ADO.NET 驱动程序将抛出异常:
6.5.5. 在事务内创建两个具有相同索引的津贴
在 [indemnites] 表中,[index] 列已声明为唯一属性,这会阻止两行拥有相同的索引。以下方法在事务内创建了两个具有相同索引的津贴:
// CreateIndemnites1
static void CreateIndemnites1() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// start of transaction
using (ITransaction transaction = session.BeginTransaction()) {
// we create two allowances with the same index
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 };
// we save them
session.Save(i1);
session.Save(i2);
// commit transaction
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
说明
- 在第 9 行和第 10 行,创建了两个具有相同索引的 Indemnites 实体。然而,在数据库中,INDEX 列具有 UNIQUE 约束。
- 第12行和第13行将这两个Indemnites实体放入持久化上下文中。当第15行提交事务时,该上下文将与数据库进行同步。此同步操作将触发两条INSERT语句。第二条语句会因INDEX列上的唯一性约束而引发异常。由于我们处于事务中,第一条INSERT语句将被回滚。
结果如下:
第 9 行:我们可以看到 [indemnites] 表是空的。尚未插入任何数据。
6.5.6. 在不使用事务的情况下创建两个具有相同索引的配额
以下方法在不使用事务的情况下创建两个具有相同索引的配额:
// CreateIndemnites2
static void CreateIndemnites2() {
try {
// session opening
using (ISession session = sessionFactory.OpenSession()) {
// we create two allowances with the same index
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 };
// we save them
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] 表中有一行。


















