【问题标题】:SQL Server ScriptDom ParsingSQL Server ScriptDom 解析
【发布时间】:2017-05-26 01:35:00
【问题描述】:

与我合作的开发人员团队正在使用 SQL 数据项目来完成我们必须针对现有数据库执行的大量工作。几周后,我们遇到了一些问题,但体验总体上还不错。

但是,当我们开始部署到生产环境时,dba 团队拒绝接受 DACPAC 作为部署方法。相反,他们希望看到每个 DML 或 DDL 语句的传统脚本。

目前的想法是在完成的 SQL 项目和生产环境之间创建一个差异脚本,然后将其解析为单独的脚本。我知道不好。

要解析差异脚本,似乎有两种选择:

  1. 根据批处理分隔符命令 GO 解析脚本。一个相当基本的解决方案,但很有希望。
  2. 或者,使用 Microsoft.SqlServer.TransactSql.ScriptDom。这看起来更具前瞻性,但似乎要复杂得多。

我目前正在试用 ScriptDom,但无法理解它。我目前的,但不仅仅是问题,如下。

我正在尝试使用 C# 中的 ScriptDOM 解析以下 SQL:

CREATE TABLE dbo.MyTable
(
    MyColumn VARCHAR(255)
)

但看不到如何访问 VARCHAR 大小,在本例中为 255。

我使用的代码如下:

TSqlFragment sqlFragment = parser.Parse(textReader, out errors);

SQLVisitor myVisitor = new SQLVisitor();
sqlFragment.Accept(myVisitor);

public override void ExplicitVisit(CreateTableStatement node)
{
    // node.SchemaObjectName.Identifiers to access the table name
    // node.Definition.ColumnDefinitions to access the column attributes
}

我希望从每个列定义中找到一个长度属性或类似属性。但是,我也偷偷怀疑您可以使用我遇到的访问者模式来重新解析每个列定义。 有什么想法吗?

【问题讨论】:

    标签: c# sql-server sql-server-data-tools dacpac


    【解决方案1】:

    太好了,您正在使用 ssdt!

    当您的 DBA 不想使用 dacpacs 时,最简单的处理方法是使用 sqlpackage.exe 预先生成部署脚本。

    我的做法是……

    • 将 t-sql 代码签入项目
    • 构建服务器构建 ssdt 项目
    • 在 ci 服务器上部署和运行测试
    • 使用 sqlpackage.exe /action:script 将 dacpac 与 QA、PROD 等进行比较并生成部署脚本。

    然后 DBA 获取该脚本(或者当我们准备好时,我们告诉他们要获取的内部版本号)- 他们可以仔细阅读和部署该脚本。

    注意事项:

    如果您没有 CI 设置,您可以只使用 sqlpackage.exe 来生成没有自动位的脚本 :)

    希望对你有帮助!

    编辑

    【讨论】:

    • 我们确实有你上面提到的所有步骤,虽然有关于使用 SQL 测试项目的“讨论”,但我离题了。我遇到的问题是 dba 每次更改都需要一个脚本文件,而不是一个包含所有更改的脚本。在您可以传递给 SQLPackage 的无数参数中,我看不到一个指定使用多个输出文件的参数。因此需要解析 SQLPackage 的输出。无论如何,谢谢。
    • 每次更改 1 个脚本?这甚至没有意义——如果你有一百个更改,他们想运行 100 个脚本?他们会以什么顺序运行它们以及您将如何处理部署后的脚本。我会尝试想办法让他们接受一个部署脚本,否则你会要求一个痛苦的世界(对他们和你)
    • 我只能同意这没有多大意义。 dba 团队有一个他们用来运行脚本的自制工具。开发人员必须完成一个 Excel 扩展,指定源控制系统中的位置、脚本运行的顺序、目标服务器和数据库。然后 DBA 使用它来部署更改。然后将输出脚本提供给开发人员。这对 dba 团队来说很好,因为它使他们的生活变得简单,但对开发人员来说却是一种痛苦。因此,DBA 团队并不急于寻找更好的解决方案,例如 dacpac 的解决方案。
    • 我感受到了你的痛苦!如果您生成单个部署脚本,您可以解析它,而不是寻找特定的语句类型(如 CreateTable),您可以使用 ParseStatementList(每个版本的解析器都有一个,所以 TSql100Parser.ParseStatementList) - 这将为您提供所有的列表单独的语句,然后您可以将它们放入自己的文件中
    • 只是好奇——他们的系统是否使用 SQLCMD 来运行脚本并且它可以在脚本中处理“GO”吗?我没有得到“每次更改一个脚本” - 这太疯狂了,无论他们多么喜欢他们的系统。如果他们能够处理运行脚本而不管其中包含什么内容,您可能会让经理谈论调整脚本所花费的时间,并按这些时间向他们的团队计费,看看他们的反应是什么。此外,有时脚本确实需要一起运行——要么全部运行,要么全部运行。他们如何在“每个脚本一个更改”模型中处理这个问题?听起来像是经理提供帮助的机会。 :)
    【解决方案2】:

    我认为您在这里根本不需要访客。如果我正确理解您的目标,您希望获取 SSDT 生成的 TSQL,使用 SQLDOM 对其进行解析,然后单独打印批次。执行此操作的代码如下所示:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using Microsoft.SqlServer.TransactSql.ScriptDom;
    
    namespace ScriptDomDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                TSql120Parser parser = new TSql120Parser(false);
                IList<ParseError> errors;
                using (StringReader sr = new StringReader(@"create table t1 (c1 int primary key)
    GO
    create table t2 (c1 int primary key)"))
                {
                    TSqlFragment fragment = parser.Parse(sr, out errors);
                    IEnumerable<string> batches = GetBatches(fragment);
                    foreach (var batch in batches)
                    {
                        Console.WriteLine(batch);
                    }
                }
            }
    
            private static IEnumerable<string> GetBatches(TSqlFragment fragment)
            {
                Sql120ScriptGenerator sg = new Sql120ScriptGenerator();
                TSqlScript script = fragment as TSqlScript;
                if (script != null)
                {
                    foreach (var batch in script.Batches)
                    {
                        yield return ScriptFragment(sg, batch);
                    }
                }
                else
                {
                    // TSqlFragment is a TSqlBatch or a TSqlStatement
                    yield return ScriptFragment(sg, fragment);
                }
            }
    
            private static string ScriptFragment(SqlScriptGenerator sg, TSqlFragment fragment)
            {
                string resultString;
                sg.GenerateScript(fragment, out resultString);
                return resultString;
            }
        }
    }
    

    至于如何使用这些 AST,我发现使用 Visual Studio 的调试器来可视化树最容易,因为您可以看到每个节点的实际类型及其所有属性。如您所见,只需要一点代码即可解析 TSQL。

    【讨论】:

    • 我使用你的代码做了一个演示,它比我自己编写代码来查找批处理终止的代码更好地解析代码,GO。我不明白的一部分是在您的 GetBatches 方法中,您有一个 IF\ELSE 语句,我看不到 ELSE 部分何时执行。感谢您的回复。
    【解决方案3】:
    #reference Microsoft.SqlServer.BatchParser
    #reference Microsoft.SqlServer.BatchParserClient
    
    using System;
    using System.Collections.Specialized;
    using System.IO;
    using System.Text;
    using Microsoft.SqlServer.Management.Common;
    
    namespace ScriptParser
    {
       class Program
       {
          static void Main(string[] args)
          {
             ExecuteBatch batcher = new ExecuteBatch();
             string text = File.ReadAllText("ASqlFile.sql");
             StringCollection statements = batcher.GetStatements(text);
             foreach (string statement in statements)
             {
                Console.WriteLine(statement);
             }
          }
       }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-28
      • 1970-01-01
      • 2013-03-18
      • 2020-04-09
      • 2021-02-18
      • 1970-01-01
      相关资源
      最近更新 更多