SqlCommand()ExecuteNonQuery()截断命令文本

我正在构建一个自定义数据库部署实用程序,我需要读取包含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); 

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

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

您如何实现解析器取决于您自己。 它可以很简单(一次读取每一行,检测除GO和空格之外的任何行)或复杂(标记所有语句并确定GO是真正的语句还是字符串中的一些文本或多行评论)。

就个人而言,我选择了第一个选项。 它可以处理您可能遇到的所有SQL文件的99%而不用担心。 如果你想要全力以赴并编写一个tokeniser,我相信很多人已经完成了一个,只有谷歌就可以了。

例:

 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; } ///  /// Return the next command batch in the file, or null if end-of-file reached. ///  public string ReadBatch() { // TODO: Implement your parsing logic here. } } 

我在搜索此问题的答案时找到了以下代码:

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(); } 

根据原帖后的评论回答:

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

这就是我们用的:)

 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 } 

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

它处理:

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

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

如果你在单词GO中添加了一个sql终止符(分号),它也会处理它

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

你这样使用它:

 using (var reader = new SqlCommandReader(scriptContents)) { var commands = new List(); reader.ReadAllCommands(c => commands.Add(c)); // commands now contains each seperated sql batch. }