C# 如何从C准确地处理SQL的批处理分隔符#

C# 如何从C准确地处理SQL的批处理分隔符#,c#,sql,tsql,parsing,C#,Sql,Tsql,Parsing,对于,我想添加对批处理分隔符的支持 例如,如果用户键入: select 'GO' go select 1 as go Go select 100 选择“开始”开始选择1作为开始 去 选择100 我想返回三个结果集 很明显,我需要某种解析器,我希望这是一个已解决的问题,我可以插入它。(我不想编写完整的T-SQL解析器) 什么组件/演示代码可以实现将此批次拆分为3个部分?我不知道现有的解决方案(尽管我同意可能有一个)。我只想指出,您可能不需要编写完整的t-SQL解析器:您真正需要找到的只是引号之

对于,我想添加对批处理分隔符的支持

例如,如果用户键入:

select 'GO' go select 1 as go Go select 100 选择“开始”开始选择1作为开始 去 选择100 我想返回三个结果集

很明显,我需要某种解析器,我希望这是一个已解决的问题,我可以插入它。(我不想编写完整的T-SQL解析器)


什么组件/演示代码可以实现将此批次拆分为3个部分?

我不知道现有的解决方案(尽管我同意可能有一个)。我只想指出,您可能不需要编写完整的t-SQL解析器:您真正需要找到的只是引号之外的单词“go”。也就是说,查找
GO
,并在过程中跟踪开盘和收盘报价。如果您找到一个匹配项,并且它不是在开始报价之后(在匹配的结束报价之前),那么它就是批处理分隔符。不用编写任何你称之为“解析器”的东西就可以很容易地做到这一点。

在上面的例子中,你不能在换行符上拆分,测试每一行是否以“go”开头,然后在上面拆分脚本吗

在重读了几遍之后,这是一个非常丑陋的问题。查看脚本中的第一行,实际上没有命令分隔符(分号或换行符)。我不认为你有太多的选择,但实际分析整个事情

但是,在这条线的某个地方,无论如何都必须对其进行解析,对吗?也许您可以在内部或使用现有的解析器来实现这一点。根据您拥有的访问权限,您可以:

  • 更改现有解析器的代码以理解“go”命令以执行并返回它所拥有的内容,然后再次运行

  • 复制一份现有的解析代码,修改它以理解“go”命令,去掉解释器部分,然后只使用它分割块并馈送到真正的解析器


我不常这样说,但在这种情况下,我绝对主张弯曲用户输入以符合计算机规则,而不是试图解决让计算机理解大量不同用户输入的问题

实施一个简单的规则: “go”一词必须出现在它自己的行中,才能被解释为继续执行的命令


如果您的用户不能遵守这样的规则,他们真的应该从事编写SQL查询这一复杂得多的任务吗?

我正在寻找相同问题的解决方案,但没有找到任何合适的解决方案(在我的情况下,使用SMO是不可接受的)。所以,我必须编写自己的解析器。这是:

static IEnumerable<string> ParseSqlBatch(Stream s)
{
    if (s == null)
        throw new ArgumentNullException();

    StringBuilder sbSqlStatement = new StringBuilder();
    Stack<string> state = new Stack<string>();
    StreamReader sr = new StreamReader(s);

    //initially search for "GO" or open tag of strings ('), comments (--, /*) or identifiers ([)
    string pattern = @"(?>(?<=^\s*)go(?=\s*(--.*)?$)|''(?!')|(?<!')'|(?<!\[)\[|--(?=.*)?|/\*)";
    //if open tag found search for close tag, then continue search
    string patternCloseString = @"(?>''|'(?!'))";
    string patternCloseIdentifier = @"(?>\]\]|\](?!\]))";
    string patternComments = @"(?>\*/|/\*)";

    Regex rx = new Regex(pattern, RegexOptions.IgnoreCase);

    while (!sr.EndOfStream)
    {
        string line = sr.ReadLine();

        int ix = 0;
        bool bBreak = false;
        while (ix < line.Length && !bBreak)
        {
            Match m = rx.Match(line, ix);

            if (!m.Success)
            {
                sbSqlStatement.Append(line.Substring(ix));
                break;
            }

            int ix2 = m.Index;
            string word = m.Value;

            sbSqlStatement.Append(line.Substring(ix, ix2 - ix));

            if (state.Count == 0)
            {
                if (string.Compare(word, "GO", true) == 0)
                {
                    if (sbSqlStatement.Length > 0)
                    {
                        yield return sbSqlStatement.ToString();
                        sbSqlStatement = new StringBuilder();
                        break;
                    }
                }
                else
                {
                    switch (word)
                    {
                        case "'":
                            rx = new Regex(patternCloseString);
                            break;
                        case "[":
                            rx = new Regex(patternCloseIdentifier);
                            break;
                        case "/*":
                            rx = new Regex(patternComments);
                            break;
                        case "--":
                            sbSqlStatement.Append(line.Substring(ix2));
                            bBreak = true;
                            continue;
                    }

                    if (word != "''")
                        state.Push(word);
                }
            }
            else
            {
                string st = state.Peek();

                switch (st)
                {
                    case "'":
                        if (st == word)
                            state.Pop();
                        break;
                    case "[":
                        if (word == "]")
                            state.Pop();
                        break;
                    case "/*":
                        if (word == "*/")
                            state.Pop();
                        else if (word == "/*")
                            state.Push(word);
                        break;
                }

                if (state.Count == 0)
                    rx = new Regex(pattern, RegexOptions.IgnoreCase);
            }

            ix = ix2 + word.Length;
            sbSqlStatement.Append(word);
        }

        sbSqlStatement.AppendLine();
    }

    if (sbSqlStatement.Length > 0)
        yield return sbSqlStatement.ToString();
}

我希望它能帮助某些人。

您可以非常轻松地将当前数据库更改为开放式SqlConnection:

 connection.ChangeDatabase("YourDB");
例如:

private static void ConctDatabase(string connectionString)
{
    using (SqlConnection conn = new SqlConnection(connectionString))
    {
        conn.Open();
        MessageBox.Show("Database: {0}", conn.Database);
        conn.ChangeDatabase("Northwind");
        MessageBox.Show("Database: {0}", conn.Database);
    }
}

yerp这可能是一种工作,但它会变得棘手,例如:选择1作为go是有效的sql it管理studiojeez。。。这一款售价为12k注:微软提供了一款TSql100Parser,但。。。就我的一生而言,我不知道如何为它提供批处理标识符。。。我倾向于只做一个琐碎但非常有限的解决方案。这会处理带引号的标识符/双引号的字符串吗?@ArinTaylor,对于双引号的字符串-是(如果它不能处理语法正确的东西,请告诉我),对于带引号的标识符-我想,不会。它不会处理像
创建表“GO”这样的情况
。我总是使用
[name]
-样式标识符,而从不使用
“name”
-样式,而且代码不是为它设计的。但它也可以很容易地修改以支持引用的标识符。只需在代码中添加
”,类似于对用于字符串的
所做的操作。from,“Transact-SQL语句不能与GO命令占用同一行。但是,行可以包含注释。”所以这已经(几乎)正确了。我关心的是相反的情况:
选择“\n go\n”;
(不能在注释中添加换行符)。
private static void ConctDatabase(string connectionString)
{
    using (SqlConnection conn = new SqlConnection(connectionString))
    {
        conn.Open();
        MessageBox.Show("Database: {0}", conn.Database);
        conn.ChangeDatabase("Northwind");
        MessageBox.Show("Database: {0}", conn.Database);
    }
}