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

SQL 脚本运行器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (13投票s)

2013 年 11 月 3 日

CPOL

4分钟阅读

viewsIcon

37614

downloadIcon

1663

一个简单的控制台应用程序,用于执行由开发人员添加的所有 SQL 文件,这些开发人员使用分散式开发环境。

引言

一个简单的控制台应用程序,用于执行由开发人员添加的所有 SQL 文件,这些开发人员使用分散式开发环境。 

背景

您如何在分散式开发环境中管理 SQL 脚本?

下图大体描述了我心中分散式开发环境的概念? 

图 1

集中式开发环境只有集中的数据存储。

分散式开发环境是拥有本地存储的那种——虚线框。

在分散式环境中,通常有一个主数据库,每个开发人员都有自己的本地副本。这种开发环境允许独立工作,无需网络连接;如果主存储出现问题,其他人仍然可以与他们的本地数据库副本一起工作。

然而,一个很大的缺点是,在开发过程中,每个开发人员都必须维护主数据库和本地数据库。这意味着需要不断创建一个包含必要更新的脚本。另一种选择是创建备份并本地恢复主数据库(这会花费大量时间,不被认为是好的做法)。
那么,如何跟踪数据库更改呢?
在本文中,我提出了两种解决方案。
1. 将脚本存储在所有开发人员都可以访问的公共存储中——例如 RedMine (https://redmine.ruby-lang.org.cn)。
2. 将所有 SQL 脚本添加到项目源代码管理中。
第一个选项将数据库脚本与具体项目分开,但不提供版本控制。 
然而,第二个选项允许进行版本控制,并且当从源代码管理中检索到最新版本的 SQL 脚本时,开发人员可以手动将其作为控制台可执行文件运行。(如果有一种方法可以自动完成此操作,那就太理想了,我将其留给大众。) 
可执行文件应该做什么?
  • 应该是可配置的——应该指定 SQL 脚本文件夹。
  • 可执行文件应该按顺序运行脚本。
  • 如果脚本存在语法错误,应用程序应该在运行脚本之前显示它——验证。
  • 如果脚本存在运行时错误,所有脚本都应该回滚。应显示错误发生的文件和行号。
  • 如果没有错误,则所有脚本都将执行并应用更改。
  • 应该有一种方法来测试结果——简单的单元测试。
要提到的一件非常重要的事情是,SQL 脚本的编写方式应该是可以多次执行的(这意味着要有检查条件或 drop and create 语句)。

使用代码 - 实现 

返回 SQL 文件列表 

目录及其子目录中的所有文件都应返回,但顺序正确——这意味着 Ver11.0 文件夹中的文件不应排在 Ver1.0 之后,如果存在 Ver2.0 文件夹。这意味着排序顺序应基于版本的值,而不是字符串值。 

这很容易通过 LINQ 表达式实现

foreach (var d in Directory.GetDirectories(dir).OrderBy(x => float.Parse(Path.GetFileName(x).Substring(Configuration.Configuration.Pproperties.ScriptVersionPrefix.Length))))
{ 

 显示执行百分比

当使用 \r 时,光标会回到当前行的开头,然后可以重写。

Console.Write("\r{0}{1}% complete", message, percent); 

如何查找 SQL 编译时错误? 

为此使用了 SQL Server Management Studio 的解析功能。我使用了两个程序集:Microsoft.Data.Schema.ScriptDom; Microsoft.Data.Schema.ScriptDom.Sql;

这两个程序集可以在以下位置找到:C:\Windows\Microsoft.NET\assembly\GAC_MSIL

如果您已安装 MS SQL Management Studio 

public static bool ParseSqlFile(string file)
{
     IList<ParseError> errors = new List<ParseError>();
     bool hasErros = false;
     using (TextReader reader = File.OpenText(file))
     {
         var parser = new TSql100Parser(true);
         var script = parser.Parse(reader, out errors) as TSqlScript;
         hasErros = errors.Count > 0;
         foreach (var parseError in errors)
         {
              Errors.ProcessErrors(file, parseError);
         }
      }
      return hasErros;
} 

应用程序的主方法 

所有内容都在主方法中集成。在这里,在打开数据库连接后,SQL 文件将在一个集合中返回,然后对每个文件,其 SQL 脚本都会被解析并作为事务的一部分执行,以便在执行不成功时数据可以回滚。

请注意

//TODO: remove
Thread.Sleep(500);

这在实际使用中应该被移除。它对演示很有用,以便可以看到执行百分比。

下一节与当前项目无关,可以跳过。如果您在获取源代码后不理解其中的任何内容,可以回顾一下。

以 OOP 方式获取配置 

在“Configuration”文件夹中可以找到三个文件,它们负责以 OOP 方式从 App.config 获取配置设置。一个自定义属性定义了设置的键,然后该属性应用于一个属性,该属性在控制台应用程序启动时通过反射进行设置。

自定义属性类

[AttributeUsage(AttributeTargets.Property, Inherited = false)]
class ConfigAttribute : Attribute
{
     /// <summary>
     /// The key
     /// </summary>
 private readonly string key;
Class with properties which have attributes of the custom attrubute type
public class ConfigProperties
{
   /// <summary>
   /// Gets or sets a value indicating whether [use AU s_ AIS].
   /// </summary>
   /// <value>
   ///   <c>true</c> if [use AU s_ AIS]; otherwise, <c>false</c>.
   /// </value>
   [Config("SQLDir")]
public string SqlDir { get; set; }  

在“Configuration.cs”文件中,通过反射读取属性。

static Configuration()
{
   properties = new ConfigProperties();
   var props = typeof(ConfigProperties).GetProperties();
   foreach (PropertyInfo prop in props)
   {
       string auth = string.Empty;
       object[] attrs = prop.GetCustomAttributes(true);
       foreach (object attr in attrs)
       {
          var authAttr = attr as ConfigAttribute;
          if (authAttr != null)
          {
             string propName = prop.Name;
             auth = authAttr.Key;
          }
   try
   {
   	var value = Convert.ChangeType(ConfigurationManager.AppSettings.Get(auth), prop.PropertyType);
       prop.SetValue(properties, value, null);
   }
   catch (Exception ex)
   {
   	throw ex;
   }
   break;
   }
 }
} 

© . All rights reserved.