16

我正在构建一个自定义数据库部署实用程序,我需要读取包含 sql 脚本的文本文件并针对数据库执行它们。

很简单的东西,到目前为止一切都很好。

但是我遇到了一个障碍,文件的内容被成功且完整地读取,但是一旦传递到 SqlCommand 然后用 SqlCommand.ExecuteNonQuery 执行,只有部分脚本被执行。

我启动了 Profiler 并确认我的代码没有通过所有脚本。

    private void ExecuteScript(string cmd, SqlConnection sqlConn, SqlTransaction trans)
    {

        SqlCommand sqlCmd = new SqlCommand(cmd, sqlConn, trans);
        sqlCmd.CommandType = CommandType.Text;
        sqlCmd.CommandTimeout = 9000000; // for testing
        sqlCmd.ExecuteNonQuery();

    }

    // I call it like this, readDMLScript contains 543 lines of T-SQL
    string readDMLScript = ReadFile(dmlFile);
    ExecuteScript(readDMLScript, sqlConn, trans);
4

6 回答 6

35

是的,每个人第一次开始将 SQL 脚本文件的内容发送到数据库时都会遇到这个问题。

GO不是 T-SQL 命令。它是所有 Microsoft 交互式 SQL 工具(Management Studio、isql、osql)都可以识别的批处理结束标记。为了处理它,您必须编写自己的解析器来分解GO语句之间文件中的每个文本块,并将它们作为单独的命令提供给数据库。

如何实现解析器取决于您。它可能很简单(一次读取每一行,检测只包含GO空格的行)或复杂的(标记所有语句并确定 aGO是真正的语句还是字符串中的一小段文本或多行注释)。

我个人选择了第一个选项。它可以毫不费力地处理您可能遇到的所有 SQL 文件的 99%。如果您想全力以赴并编写一个标记器,我敢肯定很多人已经完成了,只有谷歌才能做到。

例子:

using(var reader = new SqlBatchReader(new StreamReader(dmlFile))) {
    string batch;
    while((batch = reader.ReadBatch()) != null) {
        var cmd = new SqlCommand(batch, conn, trans) { CommandType = CommandType.Text };
        cmd.ExecuteNonQuery();
    }
}

class SqlBatchReader : IDisposable {
    private TextReader _reader;
    public SqlBatchReader(TextReader reader) {
        _reader = reader;
    }
    /// <summary>
    /// Return the next command batch in the file, or null if end-of-file reached.
    /// </summary>
    public string ReadBatch() {
        // TODO: Implement your parsing logic here.
    }
}
于 2010-03-16T20:12:37.237 回答
5

我在寻找这个问题的答案时发现了下面的代码:

http://blogs.msdn.com/b/onoj/archive/2008/02/26/incorrect-syntax-near-go-sqlcommand-executenonquery.aspx

优点:它简短易懂,非常适合我的需求。

缺点:它比基于流的解决方案效率低,并且区分大小写(即“GO”而不是“go”)。

string[] commands = sql.Split(new string[]{"GO\r\n", "GO ", "GO\t"}, StringSplitOptions.RemoveEmptyEntries );
foreach (string c in commands)
{
    var command = new SqlCommand(c, masterConnection);
    command.ExecuteNonQuery();
}
于 2013-11-18T23:06:22.257 回答
2

SMO 中的 ExecuteNonQuery 与批处理一起使用:

http://msdn.microsoft.com/en-us/library/microsoft.sqlserver.management.smo.database.executenonquery.aspx

于 2012-01-12T01:24:43.517 回答
1

根据原帖下的评论回答:

GO 是 Management Studio / osql / isql 的标记。它告诉向 SQL Server 发送一批命令。在您的实用程序中,您应该使用 GO 作为分隔符拆分输入数据并单独发送每个元素(不使用 GO 命令)

于 2010-03-16T20:00:16.513 回答
0

这就是我们使用的:)

public static class ExtensionMethodsSqlCommand
{
    #region Public

    private static bool IsGo(string psCommandLine)
    {
        if (psCommandLine == null)
            return false;
        psCommandLine = psCommandLine.Trim();
        if (string.Compare(psCommandLine, "GO", StringComparison.OrdinalIgnoreCase) == 0)
            return true;
        if (psCommandLine.StartsWith("GO", StringComparison.OrdinalIgnoreCase))
        {
            psCommandLine = (psCommandLine + "--").Substring(2).Trim();
            if (psCommandLine.StartsWith("--"))
                return true;
        }
        return false;
    }

    [System.Diagnostics.DebuggerHidden]
    public static void ExecuteNonQueryWithGos(this SqlCommand poSqlCommand)
    {
        string sCommandLong = poSqlCommand.CommandText;
        using (StringReader oStringReader = new StringReader(sCommandLong))
        {
            string sCommandLine;
            string sCommandShort = string.Empty;
            while ((sCommandLine = oStringReader.ReadLine()) != null)
                if (ExtensionMethodsSqlCommand.IsGo(sCommandLine))
                {
                    if (sCommandShort.IsNullOrWhiteSpace() == false)
                    {
                        if ((poSqlCommand.Connection.State & ConnectionState.Open) == 0)
                            poSqlCommand.Connection.Open();
                        using (SqlCommand oSqlCommand = new SqlCommand(sCommandShort, poSqlCommand.Connection))
                            oSqlCommand.ExecuteNonQuery();
                    }
                    sCommandShort = string.Empty;
                }
                else
                    sCommandShort += sCommandLine + "\r\n";
        }
    }

    #endregion Public
}
于 2013-07-22T09:26:51.233 回答
0

我最终编写了一个 StringReader 的实现来做到这一点。

它处理:

  1. 跳过破折号注释中包含的 GO
  2. 跳过斜线星评论中包含的 GO
  3. 跳过文字中包含的 GO(即单引号)
  4. 跳过列名等中包含的 GO。

因此,它只会在用作批处理分隔符时检测关键字 GO。这意味着它正确地拆分了 SQL 文本。

如果您已将 sql 终止符(分号)附加到单词 GO,它也会处理

你可以在这里找到它的代码:

你像这样使用它:

using (var reader = new SqlCommandReader(scriptContents))
       {
            var commands = new List<string>();
            reader.ReadAllCommands(c => commands.Add(c));
            // commands now contains each seperated sql batch.
        }
于 2015-02-26T21:51:56.903 回答