Skip to content

1. NHibernate ORM 入门

该文档的PDF版本可在此处获取 |HERE|。

文档中的示例可在此处获取 |HERE|。

本文是对 NHibernate 的简要介绍,它是 Java Hibernate 框架在 .NET 平台上的对应版本。如需全面介绍,请参阅:


书名:《NHibernate实战》,作者:Pierre-Henri Kuaté,出版社Manning,ISBN-13:978-1932394924


ORM(对象关系映射器)是一组库,它允许使用数据库的程序在不发出显式 SQL 命令且无需了解所用 DBMS 具体细节的情况下与数据库进行交互。


先决条件


按[初级-中级-高级]的难度等级划分,本文档属于[中级]类别。理解本文需要具备若干先决条件,相关内容可参见我撰写的部分文档:

  1. C# 2008:《使用 .NET 3.5 框架学习 C# 3.0
  1. [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] 文件夹中:

 

1.1. NHIBERNATE 在分层 .NET 架构中的作用

一个使用数据库的 .NET 应用程序可以按以下方式进行分层架构设计:

[DAO] 层通过 ADO.NET API 与数据库管理系统 (DBMS) 进行通信。让我们回顾一下该 API 的主要方法。

连接模式下,应用程序:

  1. 打开与数据源的连接
  2. 以读写模式操作数据源
  3. 关闭连接

这些操作主要涉及三个 ADO.NET 接口:

  • IDbConnection,封装了连接的属性和方法。
  • IDbCommand,封装了已执行 SQL 命令的属性和方法。
  • IDataReader,封装了 SQL SELECT 语句结果的属性和方法。

IDbConnection 接口

用于管理与数据库的连接。该接口的方法 M 和属性 P 包括以下内容:

名称
类型
角色
ConnectionString
P
数据库连接字符串。它指定了与特定数据库建立连接所需的所有参数。
打开
M
打开与由ConnectionString定义的数据库的连接
关闭
M
关闭连接
BeginTransaction
M
开始事务。
状态
P
连接状态:ConnectionState.ClosedConnectionState.OpenConnectionState.ConnectingConnectionState.ExecutingConnectionState.FetchingConnectionState.Broken

如果 Connection 是一个实现了 IDbConnection 接口的类,则可以按以下方式打开连接:

1
2
3
IDbConnection connexion=new Connection();
connexion.ConnectionString=...;
connexion.Open();

IDbCommand 接口

用于执行 SQL 语句或存储过程。该接口的方法 M 和属性 P 包括以下内容:

名称
类型
角色
CommandType
P
指定要执行的内容 - 其取值来自一个枚举:
- CommandType.Text:执行在 CommandText 属性中定义的 SQL 语句。这是默认值。
- CommandType.StoredProcedure:执行数据库中的存储过程
CommandText
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`。
ExecuteReader
M
用于执行 SELECT SQL 语句。此方法返回一个 IDataReader 对象,该对象表示 SELECT 语句的结果。
ExecuteNonQuery
M
用于执行 SQL Update、Insert 或 Delete 语句。该方法返回受操作影响的行数(更新、插入或删除的行数)。
ExecuteScalar
M
用于执行返回单个结果的 SQL SELECT 语句,例如:select count(*) from articles
CreateParameter
M
用于创建参数化 SQL 语句的 IDbParameter 参数。
Prepare
M
可让您在参数化查询使用不同参数多次执行时,优化其执行效率。

如果 Command 是一个实现了 IDbCommand 接口的类,则不使用事务执行 SQL 语句将采用以下形式:

// opening connection 
IDbConnection connexion=...
connexion.Open();
// order preparation
IDbCommand commande=new Command();
commande.Connection=connexion;
// select order execution
commande.CommandText="select ...";
IDbDataReader reader=commande.ExecuteReader();
...
// execute update, insert, delete commands
commande.CommandText="insert ...";
int nbLignesInsérées=commande.ExecuteNonQuery();
...
// locking connection
connexion.Close();

IDataReader 接口

用于封装 SQL Select 语句的结果。一个 IDataReader 对象表示一个包含行和列的表,这些行和列按顺序处理:先处理第一行,然后是第二行,依此类推。该接口的方法 (M) 和属性 (P) 包括以下内容:

名称
类型
作用
FieldCount
P
IDataReader 表中的列数
GetName
M
GetName(i) 返回 IDataReader 表中第 i 列的名称。
Item
P
Item[i] 代表 IDataReader 表中当前行的第 i 列。
读取
M
将光标移至 IDataReader 表的下一行。如果读取操作成功,则返回 True,否则返回 False
关闭
M
关闭 IDataReader 表。
GetBoolean
M
GetBoolean(i):返回 IDataReader 表当前行中第 i 列的布尔值。其他类似的方法包括:GetDateTimeGetDecimalGetDoubleGetFloatGetInt16GetInt32GetInt64GetString
Getvalue
M
Getvalue(i):将 IDataReader 表当前行中第 i 列的值作为对象类型返回。
IsDBNull
M
如果 IDataReader 表中当前行的第 i 列没有值(由 SQL NULL 值表示),则 IsDBNull(i) 返回 True

使用 IDataReader 对象通常如下所示:

// opening connection 
IDbConnection connexion=...
connexion.Open();
// order preparation
IDbCommand commande=new Command();
commande.Connection=connexion;
// select order execution
commande.CommandText="select ...";
IDataReader reader=commande.ExecuteReader();
// operation results
while(reader.Read()){
     // operate current line
        ...
}
// lock reader
reader.Close();
// locking connection
connexion.Close();

在之前的架构中,

[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]连接器即可。

1.2. 示例数据库

为了演示如何使用 NHibernate,我们将使用以下 MySQL 数据库 [dbpam_nhibernate]:

  • 在 [1] 中,该数据库包含三个表:
    • [employees]:记录日托中心员工的表
    • [contributions]:用于存储社会保险缴费率的表
    • [payroll]:用于存储计算员工薪资所需信息的表

[employees] 表

  • 在[2]中,是员工表;在[3]中,是其字段的含义

该表的内容可能如下所示:

 

表 [contributions]

  • 在[4]中,贡献表;在[5]中,其字段的含义

该表格的内容可能如下:

 

表 [indemnites]

  • 在 [6] 中,即津贴表,以及在 [7] 中,其字段的含义

该表的内容可能如下:

 

将数据库结构导出到 SQL 文件后,结果如下:

#
# Structure for the `cotisations` table : 
#

CREATE TABLE `cotisations` (
  `ID` bigint(20) NOT NULL auto_increment,
  `SECU` double NOT NULL,
  `RETRAITE` double NOT NULL,
  `CSGD` double NOT NULL,
  `CSGRDS` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

#
# Structure for the `indemnites` table : 
#

CREATE TABLE `indemnites` (
  `ID` bigint(20) NOT NULL auto_increment,
  `ENTRETIEN_JOUR` double NOT NULL,
  `REPAS_JOUR` double NOT NULL,
  `INDICE` int(11) NOT NULL,
  `INDEMNITES_CP` double NOT NULL,
  `BASE_HEURE` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `INDICE` (`INDICE`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

#
# Structure for the `employes` table : 
#

CREATE TABLE `employes` (
  `ID` bigint(20) NOT NULL auto_increment,
  `PRENOM` varchar(20) NOT NULL,
  `SS` varchar(15) NOT NULL,
  `ADRESSE` varchar(50) NOT NULL,
  `CP` varchar(5) NOT NULL,
  `VILLE` varchar(30) NOT NULL,
  `NOM` varchar(30) NOT NULL,
  `VERSION` int(11) NOT NULL,
  `INDEMNITE_ID` bigint(20) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `SS` (`SS`),
  KEY `FK_EMPLOYES_INDEMNITE_ID` (`INDEMNITE_ID`),
  CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES `indemnites` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;

请注意,在第 6、20 和 36 行中,主键 ID*<a id="autoincrement"></a> * 属性被设置为 *autoincrement*。这意味着每当添加新记录时,MySQL 会自动生成主键值。开发人员无需为此担心。

1.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] 中,测试控制台应用程序

1.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>
     <!---->
    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
        <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    </configSections>
 
 
     <!---->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
             <!-- configuration sections 
            <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>
 
     <!-- configuration NHibernate -->
     <!-- This section contains the log4net configuration settings 
    ! -->
    <log4net>
         <!-- NOTE IMPORTANTE: logs are not active by default. They must be activated programmatically with the log4net.Config.XmlConfigurator.Configure() instruction;-->
        <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] &lt;%X{auth}&gt; - %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] &lt;%X{auth}&gt; - %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] &lt;%X{auth}&gt; - %m%n"/>
            </layout>
        </appender>
 
         <!-- Define an output appender (where the logs can go) -->
        <root>
            <priority value="INFO" />
             <!-- Setup the root category, set the default priority level and add the appender(s) (where the logs will go) 
            <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 配置节。它包含两个属性:nametype

  • [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 数据库管理系统(DBMS)的方言类。
  • 在 [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]。

1.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

1.3.2.1. [contributions] 表的映射

考虑 [contributions] 表:

ID
主键,类型为 autoincrement
VERSION
记录版本号
SECU
社会保险缴费率(百分比)
PENSION
养老金缴费率
CSGD
可扣除一般社会缴费的缴费率
CSGRDS
一般社会保险费和社会债务偿还费的缴费率

本表中的一行可按如下方式封装为类型为 [ 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] 命名空间。

[cotisations] 表与 [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 字段的类型。 此处 Id 字段的类型为 int,而 int 类型的默认值为 0。因此,尚未保存的 [Cotisations] 对象(即尚未拥有主键的对象)其 Id 字段将被设置为 0。如果 Id 字段的类型为 Object 或其派生类型,则应将 unsaved-value 设置为 null。
    • 第 6 行:当 NHibernate 需要保存一个 Id 字段值为 0 的 [Cotisations] 对象时,必须在数据库上执行 INSERT 操作,在此过程中必须获取该记录主键的值。大多数数据库管理系统(DBMS)都有专有的方法来自动生成此值。<generator> 标签用于定义生成主键所采用的机制。 <generator class="native"> 标签表示应采用所用 DBMS 的默认机制。我们在第 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] 表:

ID
主键,类型为 autoincrement
VERSION
记录版本号
BASE_HOUR
每小时待命服务的费用(欧元)
DAILY_MAINTENANCE
每日津贴(欧元)
每日餐费
每日护理餐费(欧元)
带薪休假津贴
带薪休假津贴。这是按基本工资的一定比例计算的。

该表中的一行可以如下封装为类型为 [ 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 行。unique="true" 属性表明 [indemnites] 表中的 [INDICE] 列存在唯一性约束:[INDICE] 列不能有两行具有相同的值。

1.3.2.3. [employes] 表的映射

现在我们来看 [employes] 表:

ID
类型为 autoincrement 的主键
VERSION
记录版本号
FIRST_NAME
员工名字
LAST_NAME
姓氏
地址
他们的地址
邮编
他的/她的邮政编码
城市
他的/她的城市
赔偿编号
INDEMNITES(ID) 表的外键

与之前的表相比,新表的特点在于引入了外键:[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] 表有一个外键 [INDEMNITY_ID],它引用了 [INDEMNITIES] 表中的 [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]。这意味着在生成项目时,映射文件必须嵌入到生成的程序集之中。

1.4. NHibernate API

让我们回到示例项目的架构:

在前面的章节中,我们通过两种方式配置了 NHibernate:

  • 在 [App.config] 中,我们配置了数据库连接
  • 对于数据库中的每张表,我们编写了代表该表的类,以及用于在类与表之间进行转换的映射文件。

我们还需要探索 NHibernate 提供的用于操作数据库数据的方法:插入、更新、删除和查询。

1.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 管理的持久化类进行映射的文件。

1.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 代码块内(会话、事务、持久化)抛出的任何异常。

1.4.3. ISession 接口

接下来我们将介绍 NHibernate 会话实现的 ISession 接口中的部分方法:

ITransaction BeginTransaction()
在会话中启动事务
ITransaction tx = session.BeginTransaction();
void Clear()
清除会话。其中包含的对象将变为脱离状态。
session.Clear();
void Close()
关闭会话。会话中包含的对象将与数据库进行同步。此同步操作也会在事务结束时执行。后者是更常见的情况。
session.Close();
IQuery CreateQuery(string queryString)
创建一个 HQL(Hibernate 查询语言)查询,以便稍后执行。
IQuery query = session.createQuery("select e from Employee e");
void Delete(object obj)
删除一个对象。该对象可能属于会话(已附加)或不属于会话(已分离)。当会话与数据库同步时,将对该对象执行 SQL DELETE 操作。
// 从数据库中加载一名员工
Employee e = session.Get<Employee>(143);
// 删除该员工
session.Delete(e);
void Flush()
强制会话与数据库同步。会话内容不会发生改变。
session.Flush();
T Get<T>(object id)
从数据库中检索主键为 id 的对象 T。如果该对象不存在,则返回指针。
// 从数据库中加载一名员工
Employee e = session.Get<Employee>(143);
object Save(object obj)
将对象 obj 添加到会话中。在调用 Save 方法之前,该对象没有主键。调用 Save 方法后,它将拥有一个主键。当会话被同步时,将在数据库上执行一条 SQL INSERT 语句。
// 创建一名员工
Employee e = new Employee(){...};
// 保存它
e = session.Save(e);
SaveOrUpdate(object obj)
如果 obj 没有主键,则执行 Save 操作;如果已有主键,则执行 Update 操作。
void Update(object obj)
更新数据库中的对象 obj。随后将在数据库上执行 SQL UPDATE 操作。
// 从数据库中加载一名员工
Employee e = session.Get<Employee>(143);
// 更改其姓名
e.Name = ...;
// 更新数据库中的员工
session.Update(e);

1.4.4. IQuery 接口

IQuery 接口允许您查询数据库以提取数据。我们已经了解如何创建其实例:

IQuery query=session.createQuery("select e from Employe e);

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 接口的一些方法:

IList<T> List<T>()
返回查询结果,即一个 T 对象的列表
IList<Employee> employees = session.createQuery("select e from Employee e order by e.Name asc").List<Employee>();
IList List()
返回查询结果作为列表,其中每个列表元素代表 SELECT 查询中的一行,以对象数组的形式呈现。
IList rows = session.createQuery("select e.LastName, e.FirstName, e.SS from Employee e").List();
lines[i][j] 表示第 i 行第 j 列的数据,以对象形式呈现。因此,lines[10][1] 是一个表示某人名字的对象。通常需要进行类型转换,才能以确切的类型获取数据。
T UniqueResult<T>()
返回查询结果中的第一个对象
Employee e = session.createQuery("select e from Employee e where e.LastName='MARTIN'").UniqueResult<Employee>();

HQL 查询可以进行参数化

1
2
3
string numSecu;
...
Employe e=session.createQuery("select e from Employe e where e.SS=:num").SetString("num",numSecu).UniqueResult<Employe>();

在第 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)
  • ..

1.5. 一些代码示例

以下示例基于前文讨论的架构,并总结如下。数据库为同样介绍过的 MySQL 数据库 [dbpam_nhibernate]。这些示例是控制台程序 [1],使用 NHibernate 框架 [3] 来操作数据库 [2]。

包含以下示例的 C# 项目即为之前介绍过的:

  • 在 [1] 中,该项目所需的 DLL:
    • [NHibernate]:NHibernate 框架 DLL
    • [MySql.Data]:用于 MySQL 5 数据库管理系统(DBMS)的 ADO.NET 连接器 DLL
    • [log4net]:日志记录工具的 DLL
  • 在 [2] 中,表示数据库表的类
  • 在 [3] 中,是配置整个应用程序(包括 [NHibernate] 框架)的 [App.config] 文件
  • 在 [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;
 
     // 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)
      {
         // we display the exception 
        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 行:在 try 代码块finally 子句中关闭 SessionFactory。
  • 第 43 行:显示数据库内容的方法
  • 第 46 行:从 SessionFactory 获取一个 Session
  • 第 49 行:启动事务
  • 第 52 行:执行 HQL 查询以检索员工列表。由于外键将 Employee 实体与 Compensation 实体关联,因此每位员工都会有其对应的薪酬。
  • 第 60 行:用于检索津贴列表的 HQL 查询。
  • 第 68 行:HQL 查询,用于从 contributions 表中检索单行数据。
  • 第 72 行:事务结束
  • 第 73 行:结束 49 行中的 `using ITransaction` —— 事务自动关闭
  • 第 74 行:结束第 46 行开始的 `using Isession` 语句——会话自动关闭。

屏幕显示

Affichage base -------------------------------------
--------------- liste des employés
[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[1|1,93|2|3|12]]
[260124402111742|Laverti|Justine|La Brûlerie|St Marcel|49014|[2|2,1|2,1|3,1|15]]

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

请注意,在第 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;
 
     // 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 行中的 Indemnities 实体似乎没有被持久化,这可能令人感到惊讶。实际上,它们是在与 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]
[3.49|6.15|9.39|7.88]

1.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é...

1.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] 对象的所有字符串字段都将具有 null 值。在将记录插入 [employees] 表时,NHibernate 会将这些字段对应的列留空。然而,在 [employees] 表中,所有列都具有 not null 属性,这禁止列为空。因此,ADO.NET 驱动程序将抛出异常:

sauvegarde d'un employé vide -------------------------------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Employe][SQL: INSERT INTO EMPLOYES (VERSION, SS, NOM, PRENOM, ADRESSE, VILLE, CP, INDEMNITE_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?)]

1.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语句将被回滚。

结果如下:

Effacement base -------------------------------------
--------------- employee deletion
--------------- elimination of allowances
--------------- elimination of contribution rates
Création de deux indemnités de même indice dans une transaction --------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Indemnites][SQL: INSERT INTO INDEMNITES (VERSION, INDICE, BASE_HEURE, ENTRETIEN_JOUR, REPAS_JOUR, INDEMNITES_CP) VALUES (?, ?, ?, ?, ?, ?)]
Affichage base -------------------------------------
--------------- list of employees
--------------- list of benefits
--------------- table of contribution rates

第 9 行:我们可以看到 [indemnites] 表为空。尚未进行任何插入操作。

1.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 语句不会被回滚。

结果如下:

1
2
3
4
5
6
7
Création de deux indemnités de même indice sans transaction --------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Indemnites][SQL: INSERT INTO INDEMNITES (VERSION, INDICE, BASE_HEURE, ENTRETIEN_JOUR, REPAS_JOUR, INDEMNITES_CP) VALUES (?, ?, ?, ?, ?, ?)]
Affichage base -------------------------------------
--------------- list of employees
--------------- list of benefits
[1|1,93|2|3|12]
--------------- table of contribution rates

在执行该方法之前,数据库是空的。在第 6 行,我们可以看到 [indemnites] 表中有一行。