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

DBKeeperNet –保持您的数据库架构最新

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (13投票s)

2009 年 9 月 4 日

BSD

4分钟阅读

viewsIcon

52574

downloadIcon

592

一篇关于一个简单的 .NET 库的文章,该库可以使您的数据库模式保持最新。

介绍 

每个使用数据库访问的项目都需要解决如何分发数据库模式以及如何在升级后保持其最新。我曾多次解决这个问题,因此我决定编写一个通用、易于使用且免费提供的库。这就是 DbKeeperNet 库的成果。

本文将简要介绍如何使用 DbKeeperNet 库来完成此任务。该库设计为可扩展的,并计划支持任何数据库引擎。

支持的功能

  • 非常简单的用法。
  • 数据库命令保存在一个简单、结构化的 XML 文件中。
  • 每个升级步骤都在单独的事务中执行(如果数据库服务支持)。如果失败,所有后续步骤都将被禁止。
  • 内置的丰富前置条件,用于评估是否应执行更新。
  • 支持无限且可自定义的数据库引擎列表。
  • 在单个更新中,脚本可以作为 SQL 命令的替代方案,对于所有数据库引擎类型(如果需要)。
  • 支持自定义前置条件。
  • 支持自定义代码内升级步骤(允许在代码中进行复杂的数据转换,而不是在 SQL 中)。
  • DbKeeperNet 提供对当前正在发生的事情的深入日志记录。诊断输出可以通过标准的 .NET System.Diagnostics.Trace 类或 System.Diagnostics.TraceSource 类重定向,或者重定向到自定义插件,从而集成到现有的应用程序诊断框架中。
  • XML 更新脚本结构由 XSD 架构严格定义,该架构可在任何具有自动完成(智能感知)功能的 XML 编辑器中使用。
  • 支持 Log4Net 日志记录框架。
  • 支持 MySQL Connect .NET。
  • 支持 PostgreSQL。
  • 支持 SQLite。
  • 支持 Oracle XE
  • 支持 Firebird 
  • 本地化的日志消息。
  • 支持可自定义的脚本源(内置的有磁盘文件、嵌入式程序集)。

背景 

有两种基本原则可以使您应用程序的数据库模式保持最新

  • 在每次更改之前,直接在数据库中检查更改是否已进行(例如,询问数据库表是否已存在)。
  • 有一种数据库模式版本控制表,并记录当前的模式版本。

DbKeeperNet 支持这两种原则;但是,我建议使用第二种。

DbKeeperNet 在此第二种原则下的设计是为每个更新步骤提供一个唯一标识符。数据库服务实现会跟踪这些已执行的步骤(具体实现高度依赖于所使用的数据库服务)。这使得您可以非常轻松地搜索数据库并检查哪些步骤已执行。

使用 DbKeeperNet

下面的代码片段来自 DbKeeperNet.SimpleDemo 项目。如果您想直接执行演示项目,需要安装 SQL Server 2005 Express Edition,或者您必须更改 App.Config 中的连接字符串。

对于更复杂的场景,您可以查看 DbKeeperNet.ComplexDemo 项目(其中有自定义步骤实现、拆分 XML 脚本等的示例)。

我实现升级脚本的首选方式是使用存储为程序集嵌入资源的 XML 文件。因此,让我们准备一个简单的升级脚本,其中包含两个不同数据库引擎的替代语句(您可以在 DbKeeperNet.Demo 项目中找到它,文件名为 DatabaseSetup.xml

<?xml version="1.0" encoding="utf-8" ?>
<upd:Updates xmlns:upd="http://code.google.com/p/dbkeepernet/Updates-1.0.xsd"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation=
        "http://code.google.com/p/dbkeepernet/Updates-1.0.xsd Updates-1.0.xsd"
                AssemblyName="DbKeeperNet.SimpleDemo">
  <!-- Default way how to check whether to execute update step or not -->
  <DefaultPreconditions>
    <!-- We will use step information saving strategy -->
    <Precondition FriendlyName="Update step executed" 
                Precondition="StepNotExecuted"/>
  </DefaultPreconditions>
  
  <Update Version="1.00">
    <UpdateStep xsi:type="upd:UpdateDbStepType" 
    FriendlyName="Create table DbKeeperNet_SimpleDemo" Id="1">
      <!-- DbType attribute may be omitted - it will result in default value all
           which means all database types -->
      <AlternativeStatement DbType="MsSql">
        <![CDATA[
          CREATE TABLE DbKeeperNet_SimpleDemo
          (
          id int identity(1, 1) not null,
          name nvarchar(32),
          constraint PK_DbKeeperNet_SimpleDemo primary key clustered (id)
          )
        ]]>
      </AlternativeStatement>
    </UpdateStep>
    <UpdateStep xsi:type="upd:UpdateDbStepType" 
    FriendlyName="Fill table DbKeeperNet_SimpleDemo" Id="2">
      <AlternativeStatement DbType="MsSql">
        <![CDATA[
          insert into DbKeeperNet_SimpleDemo(name) values('First value');
          insert into DbKeeperNet_SimpleDemo(name) values('Second value');
        ]]>
      </AlternativeStatement>
    </UpdateStep>
  </Update>
</upd:Updates> 

现在,我们将实现代码执行所需的步骤

// Perform all configured database updates
using (UpdateContext context = new UpdateContext())
{
    context.LoadExtensions();
    context.InitializeDatabaseService("default");
 
    Updater updater = new Updater(context);
    updater.ExecuteXmlFromConfig();
}
// the above line is last required line for installation
// And now just print all inserted rows on console
// (just for demonstration purpose)
ConnectionStringSettings connectString = 
    ConfigurationManager.ConnectionStrings["default"];
 
using (SqlConnection connection = new SqlConnection(connectString.ConnectionString))
{
    connection.Open();
 
    SqlCommand cmd = connection.CreateCommand();
    cmd.CommandText = "select * from DbKeeperNet_SimpleDemo";
    SqlDataReader reader = cmd.ExecuteReader();
    while (reader.Read())
        Console.WriteLine("{0}: {1}", reader[0], reader[1]);
}

最后是 App.configWeb.Config 文件中的设置配置

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="dbkeeper.net" 
    type="DbKeeperNet.Engine.DbKeeperNetConfigurationSection,DbKeeperNet.Engine"/>
  </configSections>
  <dbkeeper.net loggingService="fx">
    <updateScripts>
      <add provider="asm" location="DbKeeperNet.SimpleDemo.DatabaseSetup.xml,DbKeeperNet.SimpleDemo" />
      <add provider="disk" location="c:\diskpath\DatabaseSetup.xml" />
    </updateScripts>
    <databaseServiceMappings>
      <add connectString="default" databaseService="MsSql" />
    </databaseServiceMappings>
  </dbkeeper.net>
  <connectionStrings>
    <add name="default" 
            connectionString="Data Source=.\SQLEXPRESS;
        AttachDbFilename='|DataDirectory|\DbKeeperNetSimpleDemo.mdf';
        Integrated Security=True;Connect Timeout=30;User Instance=True" 
            providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <system.diagnostics>
    <!-- uncomment this for TraceSource class logger (fxts)-->
    <!--
    <sources>
      <source name="DbKeeperNet" switchName="DbKeeperNet">
        <listeners>
          <add name="file" />
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="DbKeeperNet" value="Verbose"/>
    </switches>
    <sharedListeners>
      <add name="file" initializeData="dbkeepernetts.log" 
        type="System.Diagnostics.TextWriterTraceListener" />
    </sharedListeners>
    -->
    <trace autoflush="true">
      <!-- uncomment this for .NET Trace class logging (fx logger)-->
      <listeners>
        <add name="file" initializeData="dbkeepernet.log" 
        type="System.Diagnostics.TextWriterTraceListener" />
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>

就这样——所有数据库更改都会自动执行,仅在它们尚未执行的情况下。

编写数据库更新脚本

  • 如果您使用 App.Config 来指定要执行的 XML 脚本,则所有配置的脚本都将按照它们在配置文件中定义的顺序执行。同样,XML 文件的内容将按照其编写的顺序完全处理。
  • Updates 元素的 Assembly 属性实际上是一个命名空间,其中每个 VersionStep 都必须是唯一的。如果您将单个脚本逻辑地分成多个文件,可以在所有脚本中使用相同的值。
  • Update 元素的 Version 属性旨在用作数据库模式版本的标记。我建议为每个更改数据库模式的分布式构建使用唯一值(该值可以与程序集版本相同)。
  • UpdateStep 元素的 Step 属性应在每个更新版本中是唯一的。
  • 切勿在部署应用程序后更改 AssemblyNameVersionStep,除非您对自己在做什么绝对确定。 

项目位置 

如果您有任何问题、支持请求、补丁、自己的扩展,正在寻找二进制包、文档,或者正在寻找最新的源代码文件,项目托管在 http://github.com/DbKeeperNet/DbKeeperNet

您也可以将 DbKeeperNet 作为 Nuget 包引用。

结论 

本文仅展示了支持功能集中的基本用法。更多信息和升级脚本示例可以在 DbKeeperNet 源代码文件或单元测试中找到。

历史 

  • 2014 年 8 月 26 日:更新 GitHub 项目引用
  • 2014 年 7 月 17 日:项目迁移到 GitHub
  • 2012 年 9 月 23 日:功能列表更新,修复 App.Config 示例,更新源代码包 
  • 2010 年 6 月 4 日:功能列表更新,新源代码包,根据新版本更新了示例。
  • 2009 年 11 月 15 日:功能列表更新,新源代码包。
  • 2009 年 9 月 4 日:提交原始文章。 
© . All rights reserved.