2010-03-16 88 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. 
     }