Skip to content

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] 程序。我们会得到以下异常:

Exception non gérée : System.Data.MetadataException: Le schéma spécifié n'est pas valide. Erreurs :
(11,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(23,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(33,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(43,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
   à System.Data.Metadata.Edm.StoreItemCollection.Loader.ThrowOnNonWarningErrors
()
   ....
   à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
vp\Entity Framework\RdvMedecins\RdvMedecins-MySQL-01\CreateDB_01.cs:ligne 15

该错误共出现四次(第 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
2
3
4
5
6
7
8
Exception non gérée : MySql.Data.MySqlClient.MySqlException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'NOT NULL,        `ProductVersion` mediumtext NOT NULL);

ALTER TABLE `__MigrationH' at line 5
   à MySql.Data.MySqlClient.MySqlStream.ReadPacket()
   à MySql.Data.MySqlClient.NativeDriver.GetResult(Int32& affectedRow, Int32& insertedId)
   ...
   à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
vp\Entity Framework\RdvMedecins\RdvMedecins-MySQL-01\CreateDB_01.cs:ligne 15

第 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
5
6
7
8
client1--avant
Client [,xx,xx,xx,]
client1--après
Client [86,xx,xx,xx,]
client2
Client [86,xx,xx,xx,11/10/2012 11:31:12]
client3
Client [86,xx,xx,yy,11/10/2012 11:31:12]
  • 第 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 数据库管理系统