65.9K
CodeProject 正在变化。 阅读更多。
Home

SQL Server Compact (CE) 数据库维护助手

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.55/5 (4投票s)

2013 年 5 月 22 日

CPOL

4分钟阅读

viewsIcon

22291

downloadIcon

405

一个用 C# 编写的小型 .NET 类库,用于维护 SQL CE 数据库架构。

介绍 

SQL Server CE 数据库 对于需要嵌入式数据库和简单部署流程的小型项目来说可能是一个不错的选择。由于 SQL CE (版本 4) 数据库不支持批量查询,因此在应用程序新版本发布时,组织更新传播可能会变得困难或枯燥。本文介绍了一个 .NET 类库,它通过以结构化的方式提供和执行 SQL 更新命令来封装 SQL CE 数据库的维护/更新功能。命令 (SQL DML/DDL) 被包装在 XML “Update” 对象中。

这个项目的想法源于一个桌面应用程序项目,该项目已部署到多个地点,后来需要新版本并能够自主升级数据库架构。我相信桌面开发者会多次遇到同样的情况。事实上,如果使用 SQL Compact,在 Web 项目中使用它也没有什么问题。

背景

简而言之,它在应用程序生命周期中的工作方式如下:

  • 在开发过程中,数据库架构修改(以 SQL DDL/DML 的形式)会被添加到作为项目一部分(嵌入资源)的 XML 文件中,该文件具有简单且定义明确的结构(稍后呈现)。因为我们处理的是 SQL CE 数据库,所以每个命令都包含在一个单独的标签中,如下所示:
<Command>ALTER TABLE [Employee] ADD COLUMN [Department_Fk] BIGINT null;</Command>
  • 库的 `UpdateManager.ProcessUpdates` 方法必须在应用程序正常功能启动之前作为首要操作调用,这样它就可以检查/将数据库更新到最新版本。
  • XML 文件会被反序列化,然后通过 ADO .NET 一条一条地执行命令。
  • 数据库更新日志(版本信息)会持久化到库的专用表中,该表在首次使用该库时创建。
  • 客户端应用程序正常工作,直到其新版本到来,届时 `UpdateManager` 会再次检测到架构更新。

更新命令被分组在一个名为 Update 的对象中,该对象由 `SqlCEHelper.Updates.Update` 类表示。

[Serializable]
public class Update
{
    public int Number { get; set; }

    [XmlArray("Commands")]
    [XmlArrayItem("Command")]
    public List<String> Commands { get; set; }

    public string Comments { get; set; }
}

命令在一个循环中一条一条地执行。

foreach (var command in update.Commands)
{
    c.CommandText = command;
    try
    {
        c.ExecuteNonQuery();
    }
    catch (Exception exception)
    {
        var sqlCeHelperUpdateException
            = new SqlCEHelperUpdateException
                ("An error occurred while performing database updates.",
                 exception)
                {ErroredCommand = command};
        sqlCeHelperUpdateException.Data.Add(0, update);

        throw sqlCeHelperUpdateException;
    }
}

只有当所有命令都成功运行而没有异常时,更新才被认为成功。当整个更新成功运行时,一个记录会被添加到库的专用表中,其中包含更新编号,这意味着数据库的版本更高。在执行更新命令期间抛出的任何异常都将停止更新过程,并抛出 `SqlCEHelperUpdateException`。

使用代码

库代码一点也不复杂,并且非常易于使用。演示项目被封装在一个简单的 Winforms 应用程序中,并在其主窗体的 Load 事件中。没有什么能阻止开发人员将其放在其架构的任何位置,只要代码在第一次请求数据库之前运行。

要开始使用该代码,请引用 `SqlCEHelper.Updates` 二进制文件或项目源。创建 `SqlCEHelper.Updates.UpdateManager` 的实例。接下来调用 `Configure` 方法,该方法需要一个简单的 `Configuration` 对象,其中包含连接字符串和一个 `DbUpdatesLogTableName` 属性字符串。在演示代码中,我使用“ExampleSqlHelperWinFormsApplication”作为此属性的值。这是用于维护更新信息的表名。一旦选择,该属性值必须在应用程序的生命周期内保持不变。

// Intialize a new update manager
var updateManager = new UpdateManager();


// Build up the configuration
var configuration = new SqlCEHelper.Updates.Configuration ()
    {
        ConnectionString = Properties.Settings.Default.MyDatabaseConnectionString,
        // This must app app life scope variable:
        DbUpdatesLogTableName = "ExampleSqlHelperWinFormsApplication"
    };

// Configure the updater
updateManager.Configure(configuration);

可选地,客户端应用程序可以订阅某些事件,以在用户启动新版本后通知他们数据库正在更新。由于这只是一个 Winforms 应用程序,我像这里一样向文本框中添加消息。

// Show some progress of updates,
// by listeninig to the updater events
updateManager.PerformingUpdate += (s, a) =>
    {
        textBox1.AppendText(string.Format
            ("Applying database update: {0}",
                a.Update.Comments));
        textBox1.AppendText(Environment.NewLine);
    };
updateManager.PerformedUpdate += (s, a) =>
{
    textBox1.AppendText(" - success.");
    textBox1.AppendText(Environment.NewLine);
};
updateManager.DbIsUptoDate += (s, a) =>
    {
        textBox1.AppendText("The database is up to date!");
    };

最后调用 `ProcessUpdates` 方法,该方法需要一个包含更新的 XML 字符串。建议创建一个包含在解决方案中的 XML 文件,该文件维护应用程序整个生命周期的所有更改历史记录。在演示项目中,我称之为 `DbUpdates.xml`,并将其设置为嵌入资源。

<?xml version="1.0" encoding="utf-16"?>
<DatabaseUpdates xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Updates>

    <!--
      Update Number must be uniqe across application lifetime.
        Entire history of updates should remin here,
        so any old version db would get updated accordingly.
      -->

    <Update>
      <Number>1</Number>
      <Commands>
        <Command>ALTER TABLE [Employee] ADD COLUMN [DateBorn] DATETIME null;</Command>
      </Commands>
      <Comments>Added date born</Comments>
    </Update>

    <Update>
      <Number>2</Number>
      <Commands>

        <Command>ALTER TABLE [Employee] ADD COLUMN [Department_Fk] BIGINT null;</Command>

        <Command>
          create table [Department] (
          Id BIGINT IDENTITY NOT NULL,
          NAME NVARCHAR(500) NOT NULL,
          primary key (Id) );
        </Command>

        <Command>
          alter table [Employee]
          add constraint Employee_Department_Fk
          foreign key (Department_Fk)
          references [Department](Id);</Command>
      </Commands>

      <Comments>Implemented Departments and added foregin key constraint</Comments>
    </Update>

  </Updates>
</DatabaseUpdates>

这样的文件通常会驻留在数据访问层或客户端应用程序的某个位置,就像在演示中一样。因此,这是如何读取嵌入的资源 XML 并启动更新过程:

// Read the updates XML
// (The Updates XML build action must be set as Embedded Resource)
string updatesXml = (new StreamReader(Assembly.GetExecutingAssembly()
    .GetManifestResourceStream
        ("ExampleSqlHelperWinFormsApplication.Resources.DbUpdates.xml"))
        .ReadToEnd().ToString());

try
{
    // Perform updates here
    updateManager.ProcessUpdates(updatesXml);
}
catch (Exception ex)
{
    MessageBox.Show(ex.Message, "Error");
    this.Close();
}

现在应用程序可以正常继续执行,数据库已更新到最新。

应用程序首次运行时

应用程序第二次运行时

关注点

通过一些更改和重构,这段代码有可能变得更通用,并用于其他数据库。实际上,没有什么可以阻止它快速地在支持批量查询的标准 SQL Server 上运行。然而,目前该库被称为 `SQLCEHelper`,所以至少名称需要重构。

该代码已被证明可以成功更新具有数千条记录的 CE 数据库,并在多个部署环境中运行。尽管我的客户端应用程序使用 nHibernate 作为其 DAL,但它显然也能很好地工作。我意识到它仍然远非完美(特别是事件可以大大改进),但它可以作为进一步开发的起点基础,或者按原样使用。

© . All rights reserved.