4. MySQL 5.5.28 案例研究
4.1. 安装工具
需要安装的工具如下:
- 数据库管理系统(DBMS):[http://dev.mysql.com/downloads/];
- 管理工具:EMS SQL Manager for MySQL 免费版 [http://www.sqlmanager.net/fr/products/mysql/manager/download]。
在下面的示例中,root 用户的密码为 root。
让我们启动 MySQL5。这里,我们通过 Windows 服务窗口 [1] 进行操作。在 [2] 中,数据库管理系统正在运行。
![]() |
现在我们启动 [SQL Manager Lite for MySQL] 工具,我们将使用它来管理该数据库管理系统 [3]。
![]() |
- 在[4]中,我们创建一个新数据库;
- 在[5]中,我们指定数据库名称;
![]() |
- 在 [5] 中,我们以 root / root 身份登录;
- 在 [6] 中,我们验证待执行的 SQL 语句;
![]() |
- 在 [7] 中,数据库已创建。现在必须将其注册到 [EMS Manager] 中。信息正确。点击 [确定];
- 在[8]中,我们连接到该数据库;
- 在[9]中,[EMS Manager]显示了该数据库,目前该数据库为空。
接下来,我们将一个 VS 2012 项目连接到此数据库。
4.2. 根据实体创建数据库
我们创建如下所示的 VS 2012 控制台项目 [RdvMedecins-MySQL-01] [1]:
![]() |
- 在 [2] 中,通过 NuGet 为该项目添加引用;
![]() |
- 在 [3] 中,我们添加 EF 5 引用;
- 在[4]中,该文献现已列入参考文献;
![]() |
- 在 [5] 中,我们重复该过程添加 [MySQL.Data.Entities],这是一个用于 Entity Framework 的 ADO.NET 连接器。要查找该包,我们可以使用搜索框 [6];
- 在 [7] 中,出现了两个引用:[MySQL.Data.Entities] 和 [MySQL.Data],后者是前者的依赖项。
现在,我们将基于 [RdvMedecins-SqlServer-01] 项目构建 [RdvMedecins-MySQL-01] 项目。
![]() |
- 在 [1] 中,复制所选项目;
- 在 [2] 中,将它们粘贴到 [RdvMedecins-MySQL-01] 项目中;
- 在 [3] 中,由于有多个程序包含 [Main] 方法,我们需要指定启动项目。
此时,项目应能成功构建。接下来,我们将修改 [App.config] 配置文件,该文件用于配置数据库连接字符串和 DbProviderFactory。修改后内容如下:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
</entityFramework>
<!-- connecting chain-->
<connectionStrings>
<add name="monContexte"
connectionString="Server=localhost;Database=rdvmedecins-ef;Uid=root;Pwd=root;"
providerName="MySql.Data.MySqlClient" />
</connectionStrings>
<!-- the factory provider -->
<system.data>
<DbProviderFactories>
<add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=C5687FC88969C44D"
/>
</DbProviderFactories>
</system.data>
</configuration>
- 第 17 行:连接我们创建的 MySQL 数据库 [rdvmedecins-ef] 的连接字符串;
- 第 24 行:版本号必须与项目 [1] 中 [MySql.Data] 引用所对应的版本一致:
![]() |
[Entities.cs] 文件中还有一些配置,用于指定表的名称及其所属的模式。这可能因数据库管理系统(DBMS)而异。本例中即属于这种情况,即不存在模式。[Entities.cs] 文件的修改如下:
[Table("MEDECINS")]
public class Medecin : Personne
{...}
[Table("CLIENTS")]
public class Client : Personne
{...}
[Table("CRENEAUX")]
public class Creneau
{...}
[Table("RVS")]
public class Rv
{...}
让我们运行 [CreateDB_01] [2] 程序。我们会得到以下异常:
该错误共出现四次(第 2–5 行)。rowversion 类型表明实体中带有 [Timestamp] 注解的字段:
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
我们决定将这三行代码替换为以下内容:
[ConcurrencyCheck]
[Column("VERSIONING")]
public DateTime? Versioning { get; set; }
我们将列类型从 byte[] 更改为 DateTime?。之所以这样做,是因为 MySQL 提供了一个表示日期/时间的 [TIMESTAMP] 类型,且该类型的列会在每次更新行时由 MySQL 自动更新。这将使我们能够管理并发访问。
[Timestamp] 注解仅适用于 byte[] 类型的列。我们将它替换为 [ConcurrencyCheck] 注解。这两个注解都能处理并发访问。我们对所有四个实体都进行了此操作,然后重新运行应用程序。随后我们收到以下错误:
第 1 行指出 MySQL 执行的 SQL 语句存在语法错误。由于该语句并非由我们生成,而是由 MySQL ADO.NET 提供程序生成的,因此我们无法修正此问题。不过,我们可以看到下方的 [1] 中已创建了表:
![]() |
- 在 [2] 中,我们可以看到 [clients] 表的结构 [3]。
生成的数据库需要进行以下几处修改:
- [VERSIONING] 列的数据类型不正确。必须将其设置为 MySQL 的 [TIMESTAMP] 类型;
- 请注意,[rvs] 表具有唯一约束。此次生成过程中未创建该约束;
- SQL Server ADO.NET 连接器生成的外键包含 ON DELETE CASCADE 子句,而 MySQL ADO.NET 连接器并未这样做。
因此,与处理 SQL Server 时一样,我们必须修改生成的数据库。本文不展示具体修改方法,仅提供创建数据库的脚本:
# SQL Manager Lite for MySQL 5.3.0.2
# ---------------------------------------
# Host : localhost
# Port : 3306
# Database : rdvmedecins-ef
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
SET FOREIGN_KEY_CHECKS=0;
USE `rdvmedecins-ef`;
#
# Structure for the `clients` table :
#
CREATE TABLE `clients` (
`ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
`NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
`PRENOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
`TITRE` VARCHAR(5) COLLATE utf8_general_ci NOT NULL,
`VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY USING BTREE (`ID`) COMMENT ''
)ENGINE=InnoDB
AUTO_INCREMENT=96 AVG_ROW_LENGTH=4096 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
COMMENT=''
;
#
# Structure for the `medecins` table :
#
CREATE TABLE `medecins` (
`ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
`NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
`PRENOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
`TITRE` VARCHAR(5) COLLATE utf8_general_ci NOT NULL,
`VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY USING BTREE (`ID`) COMMENT ''
)ENGINE=InnoDB
AUTO_INCREMENT=56 AVG_ROW_LENGTH=4096 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
COMMENT=''
;
#
# Structure for the `creneaux` table :
#
CREATE TABLE `creneaux` (
`ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
`HDEBUT` INTEGER(11) NOT NULL,
`MDEBUT` INTEGER(11) NOT NULL,
`HFIN` INTEGER(11) NOT NULL,
`MFIN` INTEGER(11) NOT NULL,
`MEDECIN_ID` INTEGER(11) NOT NULL,
`VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY USING BTREE (`ID`) COMMENT '',
INDEX `MEDECIN_ID` USING BTREE (`MEDECIN_ID`) COMMENT '',
CONSTRAINT `creneaux_ibfk_1` FOREIGN KEY (`MEDECIN_ID`) REFERENCES `medecins` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION
)ENGINE=InnoDB
AUTO_INCREMENT=472 AVG_ROW_LENGTH=455 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
COMMENT=''
;
#
# Structure for the `rvs` table :
#
CREATE TABLE `rvs` (
`ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
`JOUR` DATE NOT NULL,
`CRENEAU_ID` INTEGER(11) NOT NULL,
`CLIENT_ID` INTEGER(11) NOT NULL,
`VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY USING BTREE (`ID`) COMMENT '',
UNIQUE INDEX `CRENEAU_ID_JOUR` USING BTREE (`JOUR`, `CRENEAU_ID`) COMMENT '',
INDEX `CRENEAU_ID` USING BTREE (`CRENEAU_ID`) COMMENT '',
INDEX `CLIENT_ID` USING BTREE (`CLIENT_ID`) COMMENT '',
CONSTRAINT `rvs_ibfk_2` FOREIGN KEY (`CLIENT_ID`) REFERENCES `clients` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT `rvs_ibfk_1` FOREIGN KEY (`CRENEAU_ID`) REFERENCES `creneaux` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION
)ENGINE=InnoDB
AUTO_INCREMENT=28 AVG_ROW_LENGTH=16384 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
COMMENT=''
;
- 第 22、38、54、74 行:表的主键(ID)为 AUTO_INCREMENT 类型,因此由 MySQL 自动生成;
- 第 26、42、60、78 行:VERSIONING 列的类型为 TIMESTAMP,并在执行 INSERT 或 UPDATE 操作时更新;
- 第 63 行:[slots] 表指向 [doctors] 表的外键,带有 ON DELETE CASCADE 子句;
- 第 80 行:[rvs] 表上的唯一性约束;
- 第 83 行:[rvs] 表指向 [slots] 表的外键,带有 ON DELETE CASCADE 子句;
- 第 84 行:[rvs] 表指向 [clients] 表的外键,带有 ON DELETE CASCADE 子句;
用于在 MySQL 数据库 [rvmedecins-ef] 中生成表的脚本已放置在 [RdvMedecins / databases / mysql] 文件夹中。读者可以加载并运行该脚本以创建表。
完成此操作后,即可运行项目中的各项程序。这些程序生成的结果与在 SQL Server 上的结果相同,但 [ModifyDetachedEntities] 程序会崩溃。要了解原因,我们可以查看 [ModifyAttachedEntities] 程序的输出:
- 第 1-2 行:保存上下文之前的客户端;
- 第 3-4 行:保存后的客户端。它具有主键,但 [Versioning] 字段没有值,而 SQL Server 更新了该实体的 [Timestamp] 字段。
现在让我们检查导致崩溃的 [ModifyDetachedEntities] 程序的代码:
using System;
...
namespace RdvMedecins_01
{
class ModifyDetachedEntities
{
static void Main(string[] args)
{
Client client1;
// empty the current base
Erase();
// add a customer
using (var context = new RdvMedecinsContext())
{
// customer creation
client1 = new Client { Titre = "x", Nom = "x", Prenom = "x" };
// add customer to context
context.Clients.Add(client1);
// save the context
context.SaveChanges();
}
// basic view
Dump("1-----------------------------");
// client1 is not in the context - we modify it
client1.Nom = "y";
// out-of-context entity modification
using (var context = new RdvMedecinsContext())
{
// here we have a new empty context
// we put client1 in the context in a modified state
context.Entry(client1).State = EntityState.Modified;
// save the context
context.SaveChanges();
}
...
}
static void Erase()
{
...
}
static void Dump(string str)
{
...
}
}
}
- 第 20 行:保存了一个客户端。它现在拥有主键,但该主键基于其版本;
- 第 33 行:对 client1 进行修改。由于其版本号与数据库中存储的不一致,修改失败。
我们通过在第25行和第26行之间插入以下代码来解决此问题:
// retrieve client1 to get its version
using (var context = new RdvMedecinsContext())
{
// customer2 will be in the context
Client client2 = context.Clients.Find(client1.Id);
// set the version of client1 to that of client2
client1.Versioning = client2.Versioning;
}
现在,[client1] 实体的版本与数据库中的版本一致,因此可以用来更新数据库中的该行。
4.3. 基于 EF 5 的多层架构
让我们回到第 2 节中描述的案例研究。
![]() |
我们将首先构建 [DAO] 数据访问层。为此,我们创建一个 VS 2012 控制台项目 [RdvMedecins-MySQL-02] [1]:
![]() |
- 在 [2] 中,通过 NuGet 添加了 [Common.Logging, EntityFramework, MySql.Data, MySql.Data.Entity, Spring.Core] 引用;
- 在 [3] 中,将 [RdvMedecins-MySQL-01] 项目中的 [Models] 文件夹复制过来;
![]() |
- 在 [4] 中,从 [RdvMedecins-SqlServer-02] 项目中复制了 [Dao、Exception、Tests] 文件夹以及 [App.config] 文件;
- 在 [5] 中,已删除 [Program.cs] 文件;
- 在 [6] 中,该项目已配置为运行 [DAO] 层的测试程序。
在 [App.config] 文件中,将 SQL Server 数据库信息替换为 MySQL 数据库信息。该信息可在 [RdvMedecins-MySQL-01] 项目的 [App.config] 文件中找到:
<!-- connecting chain-->
<connectionStrings>
<add name="monContexte"
connectionString="Server=localhost;Database=rdvmedecins-ef;Uid=root;Pwd=root;"
providerName="MySql.Data.MySqlClient" />
</connectionStrings>
<!-- the factory provider -->
<system.data>
<DbProviderFactories>
<add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=C5687FC88969C44D"
/>
</DbProviderFactories>
</system.data>
Spring 管理的对象也会发生变化。目前我们有:
<!-- spring configuration -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
</objects>
</spring>
第 7 行引用了 [RdvMedecins-SqlServer-02] 项目的程序集。该程序集现已更名为 [RdvMedecins-MySQL-02]。
完成上述操作后,我们即可运行 [DAO] 层的测试。首先,必须确保数据库已填充数据(使用 [RdvMedecins-MySQL-01] 项目中的 [Fill] 程序)。测试程序运行成功。
我们按照处理 [RdvMedecins-SqlServer-02] 项目的方法生成该项目的 DLL,并将所有项目 DLL 放置在 [RdvMedecins-MySQL-02] 目录下创建的 [lib] 文件夹中。这些 DLL 将作为 [RdvMedecins-MySQL-03] Web 项目的引用,我们将在下一部分进行讲解。
![]() |
现在,我们已准备好构建应用程序的 [ASP.NET] 层:
![]() |
我们将从 [RdvMedecins-SqlServer-03] 项目开始。我们将该项目的文件夹复制为 [RdvMedecins-MySQL-03] [1]:
![]() |
- 在 [2] 中,使用 VS 2012 Express for the Web,我们打开 [RdvMedecins-MySQL-03] 文件夹中的解决方案;
- 在 [3] 中,我们将解决方案名称和项目名称均进行修改;
![]() |
- 在 [4] 中,查看项目当前的引用;
- 在 [5] 中,我们将其删除;
- 在 [6] 中,用我们刚刚存储在 [RdvMedecins-MySQL-02] 项目内 [lib] 文件夹中的 DLL 引用替换它们。
剩下的就是修改 [Web.config] 文件。我们将该文件的当前内容替换为 [RdvMedecins-MySQL-02] 项目中 [App.config] 文件的内容。完成此操作后,运行 Web 项目。它运行正常。
4.4. 结论
让我们总结一下从 SQL Server 数据库管理系统切换到 MySQL 数据库管理系统的具体步骤:
- 用于管理实体并发访问的字段已被更改。其在 SQL Server 中的版本为:
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
现在变成了:
[ConcurrencyCheck]
[Column("VERSIONING")]
public DateTime? Versioning { get; set; }
配合 MySQL 使用;
- 将实体与表关联的 [Table] 注解已更改;
- 在 [App.config] 和 [Web.config] 配置文件中,数据库连接字符串和 [DbProviderFactory] 已进行修改;
- 在保存到数据库后,SQL Server 实体同时包含主键和时间戳。而在 MySQL 中,它仅包含主键。这需要进行代码修改。
最终,虽然修改相对较少,但我们仍需审查代码。我们正在针对另外三种数据库管理系统重复相同的流程:
- Oracle Database Express Edition 11g Release 2;
- PostgreSQL 9.2.1 数据库管理系统;
- Firebird 2.1 数据库管理系统。
















