Skip to content

7. Firebird 2.1 案例研究

7.1. 工具安装

需要安装的工具如下:

  • SGBD:[http://www.firebirdsql.org/en/firebird-2-1-5/];
  • 一个管理工具:EMS SQL Manager for InterBase/Firebird Freeware [http://www.sqlmanager.net/fr/products/ibfb/manager/download]。

在下面的示例中,用户为 sysdba,密码为 masterkey

启动 Firebird,然后启动 [SQL Manager Lite for Firebird] 工具,我们将用它来管理 SGBD。

  • 在 [1] 中,我们通过“开始”菜单启动 SGBD Firebird。此处,SGBD 未作为 Windows 服务安装;
  • 在 [2] 中,服务已启动。屏幕右下角出现了一个图标。右键单击该图标,即可停止 SGBD。

现在我们启动工具 [SQL Manager Lite for Firebird],用于管理 SGBD 和 [3]。

  • 在 [4] 中,我们创建一个新数据库;
  • 在 [5] 中,我们接受;
  • 在 [5] 中,以 SYSDBA / masterkey 身份登录;
  • 在 [6] 中,指定将要创建的文件位置。实际上,数据库将创建在一个单独的文件中;
  • 在 [7] 中,确认将要执行的 SQL 命令;
  • 在 [8] 中,数据库已创建。现在需将其保存至 [EMS Manager]。信息无误。执行 [OK];
  • 在 [9] 中,我们登录该数据库;
  • 在 [10] 中,[EMS Manager] 显示了该数据库,目前为空。

现在我们将一个2012版的VS项目连接到该数据库。

7.2. 基于实体创建数据库

首先,我们将项目 [RdvMedecins-SqlServer-01] 的文件夹复制到 [RdvMedecins-Firebird-01] 和 [1] 中:

  • 在 [2] 中,在 VS 2012 中,我们将项目 [RdvMedecins-SqlServer-01] 从解决方案中删除;
  • 在 [3] 中,该项目已被删除;
  • 在 [4] 中,我们添加了另一个项目。该项目位于我们之前创建的 [RdvMedecins-Firebird-01] 文件夹中;
  • 在 [5] 中,加载的项目名为 [RdvMedecins-SqlServer-01];
  • 在 [6] 中,将其重命名为 [RdvMedecins-Firebird-01]
  • 在 [7] 中,向解决方案中添加了另一个项目。该项目位于我们之前从解决方案中删除的 [RdvMedecins-SqlServer-01] 项目文件夹中;
  • 在 [8] 中,项目 [RdvMedecins-SqlServer-01] 已重新加入到解决方案中。

项目 [RdvMedecins-Firebird-01] 与项目 [RdvMedecins-SqlServer-01] 完全相同。我们需要进行一些修改。 在 [App.config] 中,我们将修改连接字符串,并需将 [DbProviderFactory] 适配至每个 SGBD。


<!-- 数据库连接字符串 -->
  <connectionStrings>
    <add name="monContexte" connectionString="User=SYSDBA;Password=masterkey;Database=D:\data\istia-1213\c#\dvp\Entity Framework\databases\firebird\RDVMEDECINS-EF.GDB;DataSource=localhost;
Port=3050;Dialect=3;Charset=NONE;Role=;Connection lifetime=15;Pooling=true;MinPoolSize=0;MaxPoolSize=50;Packet Size=8192;ServerType=0;" providerName="FirebirdSql.Data.FirebirdClient" />
  </connectionStrings>
  <!-- 工厂提供程序 -->
  <system.data>
    <DbProviderFactories>
      <add name="Firebird Client Data Provider" invariant="FirebirdSql.Data.FirebirdClient" description=".Net Framework Data Provider for Firebird" type="FirebirdSql.Data.FirebirdClient.FirebirdClientFactory, FirebirdSql.Data.FirebirdClient, Version=2.7.7.0, Culture=neutral, PublicKeyToken=3750abcc3150b00c" />
    </DbProviderFactories>
  </system.data>
  • 第3行:用户名、密码以及Firebird数据库的完整路径;
  • 第8-10行:DbProviderFactory。第9行引用了DLL和[FirebirdSql.Data.FirebirdClient],但我们没有这两个文件。 可通过 NuGet [1] 获取该文件:
  • 在 [2] 中,于搜索框内输入关键词 firebird
  • 在 [3] 中,选择 [Firebird ADO.NET Data Provider] 包。这是一个用于 Firebird 的 ADO.NET 连接器;
  • 在 [4] 中,新的参考编号;
  • 在 [5] 中,对于 [App.config],需填写 DLL 的正确版本号。该版本号可在其属性中找到。

在文件 [Entites.cs] 中,需调整即将生成的表的结构:


  [Table("MEDECINS")]
  public class Medecin : Personne
  {...}

  [Table("CLIENTS")]
  public class Client : Personne
  {...}

  [Table("CRENEAUX")]
  public class Creneau
  {...}

  [Table("RVS")]
  public class Rv
  {...}

此处表没有模式。

我们配置项目的运行:

  • 在 [1] 中,为即将生成的程序集指定了另一个名称;
  • 在 [2] 中,同时指定另一个默认命名空间;
  • 在 [3] 中,指定要执行的程序。

至此,编译未出现错误。运行程序 [CreateDB_01]。会得到以下异常:

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-Oracle-01\CreateDB_01.cs:ligne 15

我们记得在处理MySQL、Oracle和PostgreSQL时也遇到过同样的错误。这与实体中字段Timestamp的类型有关。 我们采用与前两个SGBD相同的修改方式。在实体中,我们将以下三行


    [Column("TIMESTAMP")]
    [Timestamp]
    public byte[] Timestamp { get; set; }

替换为以下内容:


    [ConcurrencyCheck]
    [Column("VERSIONING")]
    public int? Versioning { get; set; }

因此,我们将该列的类型从 byte[] 更改为 int?。在 SGBD 中,我们将使用存储过程,在每次插入或修改行时将该整数递增 1。

对这四个实体进行上述修改后,我们重新运行应用程序。随后出现以下错误:

1
2
3
4
5
Exception non gérée : FirebirdSql.Data.FirebirdClient.FbException: lock time-out on wait transaction object D:\DATA\ISTIA-1213\C#\DVP\ENTITY FRAMEWORK\DATABASES\FIREBIRD\RDVMEDECINS-EF.GDB 正在使用中 ---> FirebirdSql.Data.Common.IscException:等待事务的锁超时
object D:\DATA\ISTIA-1213\C#\DVP\ENTITY FRAMEWORK\DATABASES\FIREBIRD\RDVMEDECINS
-EF.GDB is in use
...
   à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\dvp\Entity Framework\RdvMedecins\RdvMedecins-Firebird-01\CreateDB_01.cs:第 15 行

第 1 行提示数据库已被占用。据我观察,此前并未出现这种情况,且我未能解决此问题。

没关系。我们将使用工具 [EMS Manager for Firebird] 手动构建数据库 [RDVMEDECINS-EF]。我们不会描述所有步骤,仅介绍最重要的部分。

Firebird 数据库结构如下:

  • 在 [1] 中,ID 作为主键,其属性为 Autoincrement。该主键将由 SGBD 自动生成;

各表的主键和外键与前面的示例中相同。外键的属性分别为 ON、DELETE 和 CASCADE。

生成器

与 Oracle 和 PostgreSQL 类似,我们创建了连续数字生成器。共有 5 个:[1]。

  • [CLIENTS_ID_GEN] 将用于生成表 [CLIENTS] 的主键;
  • [MEDECINS_ID_GEN] 将用于生成表 [MEDECINS] 的主键;
  • [CRENEAUX_ID_GEN] 将用于生成表 [CRENEAUX] 的主键;
  • [RVS_ID_GEN] 将用于生成表 [RVS] 的主键;
  • [VERSIONS_GEN] 将用于生成所有表中 [VERSIONING] 列的值。

触发器

触发器是由 SGBD 在表中发生某项事件(插入、修改、删除)之前或之后执行的存储过程。我们共有 8 个 [1]:

让我们查看触发器 [BI_CLIENTS_ID] 的代码 DDL,该触发器为表 [CLIENTS] 的列 [ID] 提供数据:

1
2
3
4
5
6
7
8
CREATE TRIGGER BI_CLIENTS_ID FOR CLIENTS
ACTIVE BEFORE INSERT
POSITION 0
AS
BEGIN
  IF (NEW.ID IS NULL) THEN
      NEW.ID = GEN_ID(CLIENTS_ID_GEN, 1);
END^
  • 第 2 行:在向表 [CLIENTS] 插入数据之前;
  • 第6-7行:如果列ID的值为NULL,则将其赋值为随机数生成器[CLIENTS_ID_GEN]的下一个值。

[ BI_CLIENTS_ID, BI_MEDECINS_ID, BI_CRENEAUX_ID, BI_RVS_ID] 触发器均按此方式构建。

现在我们来看触发器 [CLIENTS_VERSION_TRIGGER] 的代码 DDL,该触发器为表 [CLIENTS] 的列 [VERSIONING] 提供数据:

1
2
3
4
5
6
7
CREATE TRIGGER CLIENTS_VERSION_TRIGGER FOR CLIENTS
ACTIVE BEFORE INSERT OR UPDATE
POSITION 1
AS
BEGIN
  NEW."VERSIONING" = GEN_ID(VERSIONS_GEN,1);
END^
  • 第 1-3 行:在表 [CLIENTS] 上执行每次 INSERT 或 UPDATE 操作之前;
  • 第 6 行:["VERSIONING"] 列从 [VERSIONS_GEN] 随机数生成器接收以下值。该生成器为四个表的 ["VERSIONING"] 列提供数据。

触发器 [MEDECINS_VERSION_TRIGGER, CRENEAUX_VERSION_TRIGGER, RVS_VERSION_TRIGGER] 的工作原理与此类似。

Firebird 数据库表生成脚本 [RDVMEDECINS-EF] 已放置在 [RdvMedecins / databases / Firebird] 文件夹中。读者可加载并执行该脚本以创建表。

完成上述操作后,即可运行该项目的各个程序。它们产生的结果与 SQL Server 相同,但 [ModifyDetachedEntities] 程序除外——该程序会因与 Oracle 及 MySQL 出现故障时相同的原因而崩溃。 解决该问题的方法相同。只需将 [RdvMedecins-Oracle-01] 项目中的 [ModifyDetachedEntities] 程序复制到 [RdvMedecins-Firebird-01] 项目中即可。

7.3. 基于 EF 的多层架构 5

让我们回到第2段中描述的案例研究。

我们将首先构建数据访问层 [DAO]。 为此,我们将控制台项目 VS 2012 [RdvMedecins-SqlServer-02] 复制到 [RdvMedecins-Firebird-02] [1] 中:

  • 在 [2] 中,删除项目 [RdvMedecins-SqlServer-02];
  • 在 [3] 中,将现有项目添加到解决方案中。该项目取自刚刚创建的 [RdvMedecins-Firebird-02] 文件夹;
  • 在 [4] 中,新项目名称与被删除的项目相同。我们将更改其名称;
  • 在 [5] 中,我们已更改了项目名称;
  • 在 [6] 中,修改了其部分属性,例如此处的程序集名称;
  • 在 [7] 中,删除文件夹 [Models],并用项目 [RdvMedecins-Firebird-01] 中的文件夹 [Models] 替换。因为这两个项目共享相同的模板。
  • 在 [8] 中,项目当前的引用;
  • 在 [9] 中,已使用工具 NuGet 添加了 Firebird 连接器 ADO.NET。

在文件 [App.config] 中,将 SQL Server 数据库的信息替换为 Firebird 数据库的信息。这些信息可在项目 [RdvMedecins-Firebird-01] 的文件 [App.config] 中找到:


<!-- 数据库连接字符串 -->
  <connectionStrings>
    <add name="monContexte" connectionString="User=SYSDBA;Password=masterkey;Database=D:\data\istia-1213\c#\dvp\Entity Framework\databases\firebird\RDVMEDECINS-EF.GDB;DataSource=localhost;
Port=3050;Dialect=3;Charset=NONE;Role=;Connection lifetime=15;Pooling=true;MinPoolSize=0;MaxPoolSize=50;Packet Size=8192;ServerType=0;" providerName="FirebirdSql.Data.FirebirdClient" />
  </connectionStrings>
  <!-- 工厂提供程序 -->
  <system.data>
    <DbProviderFactories>
      <add name="Firebird Client Data Provider" invariant="FirebirdSql.Data.FirebirdClient" description=".Net Framework Data Provider for Firebird" type="FirebirdSql.Data.FirebirdClient.FirebirdClientFactory, FirebirdSql.Data.FirebirdClient, Version=2.7.7.0, Culture=neutral, PublicKeyToken=3750abcc3150b00c" />
    </DbProviderFactories>
  </system.data>

Spring管理的对象也会发生变化。目前我们有:


  <!-- Spring配置 -->
  <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-Firebird-02]。

完成上述操作后,我们已准备好执行 [DAO] 层的测试。在此之前,需先确保填充好基础数据(即 [RdvMedecins-Firebird-01] 项目中的 [Fill] 程序)。测试程序通过。

我们按照之前处理项目[RdvMedecins-SqlServer-02]的方法创建该项目中的DLL,并将项目中所有 DLL 程序,将其归入在 [RdvMedecins-Firebird-02] 中创建的 [lib] 文件夹中。这些将作为后续 Web 项目 [RdvMedecins-Firebird-03] 的参考。

  

现在,我们已准备好构建应用程序的 [ASP.NET] 层:

我们将以项目 [RdvMedecins-SqlServer-03] 为基础。我们将该项目的文件夹复制到 [RdvMedecins-Firebird-03] 和 [1] 中:

  • 在 [2] 中,使用 VS 2012 Express for the Web,我们打开 [RdvMedecins-Firebird-03] 文件夹中的解决方案;
  • 在 [3] 中,我们将解决方案名称和项目名称进行修改;
  • 在 [4] 中,当前项目的引用;
  • 在 [5] 中,将其删除;
  • 并将其替换为对 DLL 的引用,该文件我们刚刚存储在项目 [RdvMedecins-Firebird-02] 的文件夹 [lib] 中。

接下来只需修改文件 [Web.config],将其当前内容替换为项目 [RdvMedecins-Firebird-02] 中文件 [App.config] 的内容。完成上述操作后,运行 Web 项目。项目运行正常。 请务必在运行Web应用程序前先填充数据库。