2010-03-16 86 views
16

我正在构建自定义数据库部署实用程序,我需要读取包含sql脚本的文本文件并对数据库执行它们。SqlCommand()ExecuteNonQuery()截断命令文本

很容易的东西,迄今为止很好。

但是我遇到了一个障碍,文件的内容被成功完全读取,但一旦传入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); 
+1

脚本截断了什么字符? – MikeWyatt 2010-03-16 17:21:51

+1

'ReadFile'方法是如何工作的?你是200%确定它不会跳过几个字符,也许?为什么不使用'System.IO.File.ReadAllText(filename)'? – 2010-03-16 17:24:42

+0

你从文件中读取多少*文本,以字节为单位? – 2010-03-16 17:32:18

回答

35

是的,每个人在第一次开始将SQL脚本文件的内容发送到数据库。

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

如何实现解析器取决于您。它可能很简单(一次读取每行,检测只包含GO和空白的行)或复杂(将所有语句标记化,并确定GO是否是字符串中的真实语句或一些文本或多行评论)。

就我个人而言,我选择了第一个选项。它可以处理您可能遇到的所有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. 
    } 
} 
+0

除非您的脚本文件以'SET IDENTITY_INSERT ON'开头。有没有好的解决方案将数据导出到.NET文件然后导入? – MStodd 2015-05-29 15:59:35

+0

@MStodd:'SET IDENTITY_INSERT'旨在用于数据导入脚本,因此它是一个很好的解决方案。但个人而言,我会使用SSIS导入数据。 – 2015-06-01 07:36:31

+0

现在我遇到的问题是,如果我从SSMS以'SET IDENTITY_INSERT ON'开始导出脚本,它将不足以使用您的代码吗?在每次插入之前,我不需要执行该命令吗? – MStodd 2015-06-01 17:00:14

1

回答下原职基础上留言:

GO是管理工作室/ OSQL/ISQL的标志。它告诉将一批命令发送到SQL Server。在你的实用程序中,你应该使用GO作为分隔符分割输入数据,并单独发送每个元素(不使用GO命令)

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 
} 
+0

为什么不添加一些你已经发布的代码示例的描述?阅读描述并理解解决方案的工作或不适合您的工作比花时间分析代码的工作更容易。 – Artemix 2013-07-22 09:48:50

5

我发现下面的代码,而搜索对于这个问题的答案:

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

优点:简单易懂,完全满足我的需求。

缺点:它比基于Stream的解决方案效率低,并且区分大小写(即“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(); 
} 
0

我写了一个StringReader的实现来完成这个任务。

它处理:

  1. 跳过走过去包含在短跑冲刺评论
  2. 跳过走过去包含在斜线星级评论
  3. 跳过走过去包含在文字(即单引号)
  4. 跳绳过去GO包含在列名等

因此,它只会检测关键字GO whe n用作批量分离器。这意味着它正确地分割SQL文本。

它还处理,如果你已经附加了SQL终止(分号)字GO

你可以找到它here代码:您可以使用它像这样

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