【问题标题】:Execute a large SQL script (with GO commands)执行大型 SQL 脚本(使用 GO 命令)
【发布时间】:2010-09-07 15:15:59
【问题描述】:

我需要在 C# 程序中执行大量 SQL 语句(创建一堆表、视图和存储过程)。

这些语句需要用GO语句分隔,但SqlCommand.ExecuteNonQuery()不喜欢GO语句。我的解决方案(我想我会发布以供参考)是在 GO 行上拆分 SQL 字符串,并分别执行每个批处理。

有没有更简单/更好的方法?

【问题讨论】:

    标签: c# sql-server


    【解决方案1】:

    您可以在每个语句的末尾使用;,因为它对我有用。 真的不知道有没有什么缺点。

    【讨论】:

    • 并非如此。例如,您必须在每个语句的开头有CREATE FUNCTION。在这种情况下,用; 终止最后一个是行不通的。
    【解决方案2】:

    对于仍有问题的任何人。你可以使用微软官方 SMO

    https://docs.microsoft.com/en-us/sql/relational-databases/server-management-objects-smo/overview-smo?view=sql-server-2017

    using (var connection = new SqlConnection(connectionString))
    {
      var server = new Server(new ServerConnection(connection));
      server.ConnectionContext.ExecuteNonQuery(sql);
    }
    

    【讨论】:

    • 这并没有添加任何超过最高投票、接受的答案,这也暗示了 SMO(10 年前发布!)。
    【解决方案3】:

    我看了几遍最后决定用EF implementationSqlConnection做了一点修改

    public static void ExecuteSqlScript(this SqlConnection sqlConnection, string sqlBatch)
            {
                // Handle backslash utility statement (see http://technet.microsoft.com/en-us/library/dd207007.aspx)
                sqlBatch = Regex.Replace(sqlBatch, @"\\(\r\n|\r|\n)", string.Empty);
    
                // Handle batch splitting utility statement (see http://technet.microsoft.com/en-us/library/ms188037.aspx)
                var batches = Regex.Split(
                    sqlBatch,
                    string.Format(CultureInfo.InvariantCulture, @"^\s*({0}[ \t]+[0-9]+|{0})(?:\s+|$)", BatchTerminator),
                    RegexOptions.IgnoreCase | RegexOptions.Multiline);
    
                for (int i = 0; i < batches.Length; ++i)
                {
                    // Skip batches that merely contain the batch terminator
                    if (batches[i].StartsWith(BatchTerminator, StringComparison.OrdinalIgnoreCase) ||
                        (i == batches.Length - 1 && string.IsNullOrWhiteSpace(batches[i])))
                    {
                        continue;
                    }
    
                    // Include batch terminator if the next element is a batch terminator
                    if (batches.Length > i + 1 &&
                        batches[i + 1].StartsWith(BatchTerminator, StringComparison.OrdinalIgnoreCase))
                    {
                        int repeatCount = 1;
    
                        // Handle count parameter on the batch splitting utility statement
                        if (!string.Equals(batches[i + 1], BatchTerminator, StringComparison.OrdinalIgnoreCase))
                        {
                            repeatCount = int.Parse(Regex.Match(batches[i + 1], @"([0-9]+)").Value, CultureInfo.InvariantCulture);
                        }
    
                        for (int j = 0; j < repeatCount; ++j)
                        {
                           var command = sqlConnection.CreateCommand();
                           command.CommandText = batches[i];
                           command.ExecuteNonQuery();
                        }
                    }
                    else
                    {
                        var command = sqlConnection.CreateCommand();
                        command.CommandText = batches[i];
                        command.ExecuteNonQuery();
                    }
                }
            }
    

    【讨论】:

    • 谢谢@Filip Cordas。尽管这没有被标记为答案,但这对我很有帮助!我们有大量脚本,其中提到 BatchTerminator 的方式不同,例如大写和小写的组合(go、Go、GO 等),并且最大时间带有尾随或前导空格,这导致通过 c# 执行时出现大问题...... . 谢谢!!
    • @DipakRiswadkar 是的,在这个问题上锁定了几次,提供的答案都没有满足我的需求,所以看了看 EF 实现看起来不错,所以我发布了答案。
    • 很棒的答案,它就像一个魅力,非常感谢
    • @Really 也应该将其报告给实体框架团队。正如我所说,这只是过去的复制,几乎没有修改。
    【解决方案4】:

    为了避免第三方、正则表达式、内存开销和快速处理大型脚本,我创建了自己的基于流的解析器。它

    • 检查之前的语法
    • 可以用 -- 或 /**/ 识别 cmets

      -- some commented text
       /*
      drop table Users;
      GO
         */
      
    • 可以识别带有 ' 或 "的字符串文字

      set @s =
          'create table foo(...);
          GO
          create index ...';
      
    • 保留 LF 和 CR 格式
    • 在对象主体(存储过程、视图等)中保留 cmets 块
    • 和其他结构,例如

            gO -- commented text
      

    如何使用

        try
        {
            using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Persist Security Info=True;Initial Catalog=DATABASE-NAME;Data Source=SERVER-NAME"))
            {
                connection.Open();
    
                int rowsAffected = SqlStatementReader.ExecuteSqlFile(
                    "C:\\target-sql-script.sql",
                    connection,
                    // Don't forget to use the correct file encoding!!!
                    Encoding.Default,
                    // Indefinitely (sec)
                    0
                );
            }
        }
        // implement your handlers
        catch (SqlStatementReader.SqlBadSyntaxException) { }
        catch (SqlException) { }
        catch (Exception) { }
    

    基于流的 SQL 脚本阅读器

    class SqlStatementReader
    {
        public class SqlBadSyntaxException : Exception
        {
            public SqlBadSyntaxException(string description) : base(description) { }
            public SqlBadSyntaxException(string description, int line) : base(OnBase(description, line, null)) { }
            public SqlBadSyntaxException(string description, int line, string filePath) : base(OnBase(description, line, filePath)) { }
            private static string OnBase(string description, int line, string filePath)
            {
                if (filePath == null)
                    return string.Format("Line: {0}. {1}", line, description);
                else
                    return string.Format("File: {0}\r\nLine: {1}. {2}", filePath, line, description);
            }
        }
    
        enum SqlScriptChunkTypes
        {
            InstructionOrUnquotedIdentifier = 0,
            BracketIdentifier = 1,
            QuotIdentifierOrLiteral = 2,
            DblQuotIdentifierOrLiteral = 3,
            CommentLine = 4,
            CommentMultiline = 5,
        }
    
        StreamReader _sr = null;
        string _filePath = null;
        int _lineStart = 1;
        int _lineEnd = 1;
        bool _isNextChar = false;
        char _nextChar = '\0';
    
        public SqlStatementReader(StreamReader sr)
        {
            if (sr == null)
                throw new ArgumentNullException("StreamReader can't be null.");
    
            if (sr.BaseStream is FileStream)
                _filePath = ((FileStream)sr.BaseStream).Name;
    
            _sr = sr;
        }
    
        public SqlStatementReader(StreamReader sr, string filePath)
        {
            if (sr == null)
                throw new ArgumentNullException("StreamReader can't be null.");
    
            _sr = sr;
            _filePath = filePath;
        }
    
        public int LineStart { get { return _lineStart; } }
        public int LineEnd { get { return _lineEnd == 1 ? _lineEnd : _lineEnd - 1; } }
    
        public void LightSyntaxCheck()
        {
            while (ReadStatementInternal(true) != null) ;
        }
    
        public string ReadStatement()
        {
            for (string s = ReadStatementInternal(false); s != null; s = ReadStatementInternal(false))
            {
                // skip empty
                for (int i = 0; i < s.Length; i++)
                {
                    switch (s[i])
                    {
                        case ' ': continue;
                        case '\t': continue;
                        case '\r': continue;
                        case '\n': continue;
                        default:
                            return s;
                    }
                }
            }
            return null;
        }
    
        string ReadStatementInternal(bool syntaxCheck)
        {
            if (_isNextChar == false && _sr.EndOfStream)
                return null;
    
            StringBuilder allLines = new StringBuilder();
            StringBuilder line = new StringBuilder();
            SqlScriptChunkTypes nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
            SqlScriptChunkTypes currentChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
            char ch = '\0';
            int lineCounter = 0;
            int nextLine = 0;
            int currentLine = 0;
            bool nextCharHandled = false;
            bool foundGO;
            int go = 1;
    
            while (ReadChar(out ch))
            {
                if (nextCharHandled == false)
                {
                    currentChunk = nextChunk;
                    currentLine = nextLine;
    
                    switch (currentChunk)
                    {
                        case SqlScriptChunkTypes.InstructionOrUnquotedIdentifier:
    
                            if (ch == '[')
                            {
                                currentChunk = nextChunk = SqlScriptChunkTypes.BracketIdentifier;
                                currentLine = nextLine = lineCounter;
                            }
                            else if (ch == '"')
                            {
                                currentChunk = nextChunk = SqlScriptChunkTypes.DblQuotIdentifierOrLiteral;
                                currentLine = nextLine = lineCounter;
                            }
                            else if (ch == '\'')
                            {
                                currentChunk = nextChunk = SqlScriptChunkTypes.QuotIdentifierOrLiteral;
                                currentLine = nextLine = lineCounter;
                            }
                            else if (ch == '-' && (_isNextChar && _nextChar == '-'))
                            {
                                nextCharHandled = true;
                                currentChunk = nextChunk = SqlScriptChunkTypes.CommentLine;
                                currentLine = nextLine = lineCounter;
                            }
                            else if (ch == '/' && (_isNextChar && _nextChar == '*'))
                            {
                                nextCharHandled = true;
                                currentChunk = nextChunk = SqlScriptChunkTypes.CommentMultiline;
                                currentLine = nextLine = lineCounter;
                            }
                            else if (ch == ']')
                            {
                                throw new SqlBadSyntaxException("Incorrect syntax near ']'.", _lineEnd + lineCounter, _filePath);
                            }
                            else if (ch == '*' && (_isNextChar && _nextChar == '/'))
                            {
                                throw new SqlBadSyntaxException("Incorrect syntax near '*'.", _lineEnd + lineCounter, _filePath);
                            }
                            break;
    
                        case SqlScriptChunkTypes.CommentLine:
    
                            if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
                            {
                                nextCharHandled = true;
                                currentChunk = nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                                currentLine = nextLine = lineCounter;
                            }
                            else if (ch == '\n' || ch == '\r')
                            {
                                currentChunk = nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                                currentLine = nextLine = lineCounter;
                            }
                            break;
    
                        case SqlScriptChunkTypes.CommentMultiline:
    
                            if (ch == '*' && (_isNextChar && _nextChar == '/'))
                            {
                                nextCharHandled = true;
                                nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                                nextLine = lineCounter;
                            }
                            else if (ch == '/' && (_isNextChar && _nextChar == '*'))
                            {
                                throw new SqlBadSyntaxException("Missing end comment mark '*/'.", _lineEnd + currentLine, _filePath);
                            }
                            break;
    
                        case SqlScriptChunkTypes.BracketIdentifier:
    
                            if (ch == ']')
                            {
                                nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                                nextLine = lineCounter;
                            }
                            break;
    
                        case SqlScriptChunkTypes.DblQuotIdentifierOrLiteral:
    
                            if (ch == '"')
                            {
                                if (_isNextChar && _nextChar == '"')
                                {
                                    nextCharHandled = true;
                                }
                                else
                                {
                                    nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                                    nextLine = lineCounter;
                                }
                            }
                            break;
    
                        case SqlScriptChunkTypes.QuotIdentifierOrLiteral:
    
                            if (ch == '\'')
                            {
                                if (_isNextChar && _nextChar == '\'')
                                {
                                    nextCharHandled = true;
                                }
                                else
                                {
                                    nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
                                    nextLine = lineCounter;
                                }
                            }
                            break;
                    }
                }
                else
                    nextCharHandled = false;
    
                foundGO = false;
                if (currentChunk == SqlScriptChunkTypes.InstructionOrUnquotedIdentifier || go >= 5 || (go == 4 && currentChunk == SqlScriptChunkTypes.CommentLine))
                {
                    // go = 0 - break, 1 - begin of the string, 2 - spaces after begin of the string, 3 - G or g, 4 - O or o, 5 - spaces after GO, 6 - line comment after valid GO
                    switch (go)
                    {
                        case 0:
                            if (ch == '\r' || ch == '\n')
                                go = 1;
                            break;
                        case 1:
                            if (ch == ' ' || ch == '\t')
                                go = 2;
                            else if (ch == 'G' || ch == 'g')
                                go = 3;
                            else if (ch != '\n' && ch != '\r')
                                go = 0;
                            break;
                        case 2:
                            if (ch == 'G' || ch == 'g')
                                go = 3;
                            else if (ch == '\n' || ch == '\r')
                                go = 1;
                            else if (ch != ' ' && ch != '\t')
                                go = 0;
                            break;
                        case 3:
                            if (ch == 'O' || ch == 'o')
                                go = 4;
                            else if (ch == '\n' || ch == '\r')
                                go = 1;
                            else
                                go = 0;
                            break;
                        case 4:
                            if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
                                go = 5;
                            else if (ch == '\n' || ch == '\r')
                                foundGO = true;
                            else if (ch == ' ' || ch == '\t')
                                go = 5;
                            else if (ch == '-' && (_isNextChar && _nextChar == '-'))
                                go = 6;
                            else
                                go = 0;
                            break;
                        case 5:
                            if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
                                go = 5;
                            else if (ch == '\n' || ch == '\r')
                                foundGO = true;
                            else if (ch == '-' && (_isNextChar && _nextChar == '-'))
                                go = 6;
                            else if (ch != ' ' && ch != '\t')
                                throw new SqlBadSyntaxException("Incorrect syntax was encountered while parsing go.", _lineEnd + lineCounter, _filePath);
                            break;
                        case 6:
                            if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
                                go = 6;
                            else if (ch == '\n' || ch == '\r')
                                foundGO = true;
                            break;
                        default:
                            go = 0;
                            break;
                    }
                }
                else
                    go = 0;
    
                if (foundGO)
                {
                    if (ch == '\r' || ch == '\n')
                    {
                        ++lineCounter;
                    }
                    // clear GO
                    string s = line.Append(ch).ToString();
                    for (int i = 0; i < s.Length; i++)
                    {
                        switch (s[i])
                        {
                            case ' ': continue;
                            case '\t': continue;
                            case '\r': continue;
                            case '\n': continue;
                            default:
                                _lineStart = _lineEnd;
                                _lineEnd += lineCounter;
                                return allLines.Append(s.Substring(0, i)).ToString();
                        }
                    }
                    return string.Empty;
                }
    
                // accumulate by string
                if (ch == '\r' && (_isNextChar == false || _nextChar != '\n'))
                {
                    ++lineCounter;
                    if (syntaxCheck == false)
                        allLines.Append(line.Append('\r').ToString());
                    line.Clear();
                }
                else if (ch == '\n')
                {
                    ++lineCounter;
                    if (syntaxCheck == false)
                        allLines.Append(line.Append('\n').ToString());
                    line.Clear();
                }
                else
                {
                    if (syntaxCheck == false)
                        line.Append(ch);
                }
            }
    
            // this is the end of the stream, return it without GO, if GO exists
            switch (currentChunk)
            {
                case SqlScriptChunkTypes.InstructionOrUnquotedIdentifier:
                case SqlScriptChunkTypes.CommentLine:
                    break;
                case SqlScriptChunkTypes.CommentMultiline:
                    if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
                        throw new SqlBadSyntaxException("Missing end comment mark '*/'.", _lineEnd + currentLine, _filePath);
                    break;
                case SqlScriptChunkTypes.BracketIdentifier:
                    if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
                        throw new SqlBadSyntaxException("Unclosed quotation mark [.", _lineEnd + currentLine, _filePath);
                    break;
                case SqlScriptChunkTypes.DblQuotIdentifierOrLiteral:
                    if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
                        throw new SqlBadSyntaxException("Unclosed quotation mark \".", _lineEnd + currentLine, _filePath);
                    break;
                case SqlScriptChunkTypes.QuotIdentifierOrLiteral:
                    if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
                        throw new SqlBadSyntaxException("Unclosed quotation mark '.", _lineEnd + currentLine, _filePath);
                    break;
            }
    
            if (go >= 4)
            {
                string s = line.ToString();
                for (int i = 0; i < s.Length; i++)
                {
                    switch (s[i])
                    {
                        case ' ': continue;
                        case '\t': continue;
                        case '\r': continue;
                        case '\n': continue;
                        default:
                            _lineStart = _lineEnd;
                            _lineEnd += lineCounter + 1;
                            return allLines.Append(s.Substring(0, i)).ToString();
                    }
                }
            }
    
            _lineStart = _lineEnd;
            _lineEnd += lineCounter + 1;
            return allLines.Append(line.ToString()).ToString();
        }
    
        bool ReadChar(out char ch)
        {
            if (_isNextChar)
            {
                ch = _nextChar;
                if (_sr.EndOfStream)
                    _isNextChar = false;
                else
                    _nextChar = Convert.ToChar(_sr.Read());
                return true;
            }
            else if (_sr.EndOfStream == false)
            {
                ch = Convert.ToChar(_sr.Read());
                if (_sr.EndOfStream == false)
                {
                    _isNextChar = true;
                    _nextChar = Convert.ToChar(_sr.Read());
                }
                return true;
            }
            else
            {
                ch = '\0';
                return false;
            }
        }
    
        public static int ExecuteSqlFile(string filePath, SqlConnection connection, Encoding fileEncoding, int commandTimeout)
        {
            int rowsAffected = 0;
            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                // Simple syntax check (you can comment out these two lines below)
                new SqlStatementReader(new StreamReader(fs, fileEncoding)).LightSyntaxCheck();
                fs.Seek(0L, SeekOrigin.Begin);
    
                // Read statements without GO
                SqlStatementReader rd = new SqlStatementReader(new StreamReader(fs, fileEncoding));
                string stmt;
                while ((stmt = rd.ReadStatement()) != null)
                {
                    using (SqlCommand cmd = connection.CreateCommand())
                    {
                        cmd.CommandText = stmt;
                        cmd.CommandTimeout = commandTimeout;
                        int i = cmd.ExecuteNonQuery();
                        if (i > 0)
                            rowsAffected += i;
                    }
                }
            }
            return rowsAffected;
        }
    }
    

    【讨论】:

      【解决方案5】:

      如果你不想使用 SMO,比如你需要跨平台,你也可以使用 SubText 中的 ScriptSplitter 类。

      Here's C# & VB.NET 中的实现

      用法:

          string strSQL = @"
      SELECT * FROM INFORMATION_SCHEMA.columns
      GO
      SELECT * FROM INFORMATION_SCHEMA.views
      ";
      
          foreach(string Script in new Subtext.Scripting.ScriptSplitter(strSQL ))
          {
              Console.WriteLine(Script);
          }
      

      如果您对多行 c 样式 cmets 有问题,请使用正则表达式删除 cmets:

      static string RemoveCstyleComments(string strInput)
      {
          string strPattern = @"/[*][\w\d\s]+[*]/";
          //strPattern = @"/\*.*?\*/"; // Doesn't work
          //strPattern = "/\\*.*?\\*/"; // Doesn't work
          //strPattern = @"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/ "; // Doesn't work
          //strPattern = @"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/ "; // Doesn't work
      
          // http://stackoverflow.com/questions/462843/improving-fixing-a-regex-for-c-style-block-comments
          strPattern = @"/\*(?>(?:(?>[^*]+)|\*(?!/))*)\*/";  // Works !
      
          string strOutput = System.Text.RegularExpressions.Regex.Replace(strInput, strPattern, string.Empty, System.Text.RegularExpressions.RegexOptions.Multiline);
          Console.WriteLine(strOutput);
          return strOutput;
      } // End Function RemoveCstyleComments
      

      删除单行 cmets 在这里:

      https://stackoverflow.com/questions/9842991/regex-to-remove-single-line-sql-comments
      

      【讨论】:

      • 这个类是否考虑/* Go */的情况?
      • @cMinor:不在拆分器中,但您可以在拆分之前使用正则表达式删除多行 cmets。
      【解决方案6】:

      使用能够理解 GO 分隔符的 SQL Server 管理对象 (SMO)。在此处查看我的博文:http://weblogs.asp.net/jongalloway/Handling-_2200_GO_2200_-Separators-in-SQL-Scripts-2D00-the-easy-way

      示例代码:

      public static void Main()    
      {        
        string scriptDirectory = "c:\\temp\\sqltest\\";
        string sqlConnectionString = "Integrated Security=SSPI;" +
        "Persist Security Info=True;Initial Catalog=Northwind;Data Source=(local)";
        DirectoryInfo di = new DirectoryInfo(scriptDirectory);
        FileInfo[] rgFiles = di.GetFiles("*.sql");
        foreach (FileInfo fi in rgFiles)
        {
              FileInfo fileInfo = new FileInfo(fi.FullName);
              string script = fileInfo.OpenText().ReadToEnd();
              using (SqlConnection connection = new SqlConnection(sqlConnectionString))
              {
                  Server server = new Server(new ServerConnection(connection));
                  server.ConnectionContext.ExecuteNonQuery(script);
              }
         }
      }
      

      如果这对您不起作用,请参阅处理该问题的 Phil Haack 的库:http://haacked.com/archive/2007/11/04/a-library-for-executing-sql-scripts-with-go-separators-and.aspx

      【讨论】:

      • 如何与事务集成?当使用 SqlConnection 创建 ServerConnection 时,该代码会引发 InvalidOperationException,其中包含挂起的事务。
      • 此解决方案有效,我只想补充一点,如果您想将 transactionsTransactionScope 对象一起使用,您只需要注册与当前环境事务的连接即可。在这里查看我的答案:stackoverflow.com/a/18322938/1268570
      • 效果很好,但是我们可以使用 SqlConnection.InfoMessage) 在 C# 应用程序中查看结果将结果保存在 txt 文件中,以了解脚本是否成功执行,因为最近在远程主机上执行 150 mb 脚本文件时使用sqlcmd,55 分钟后,一些行受到此错误的影响,TCP Provider: An existing connection was forcibly closed by the remote host.communication link failure。 ,不知道影响的行数,但我担心运行数据库生成的脚本文件时出现错误消息。
      • 当机器上没有安装某些 SQL Dll 时,此解决方案会导致您的代码失败。 .NET 使用 Windows 中内置的一些 dll。某些 SQL 功能包(包括管理对象)的缺失可能会阻止类似“未找到 Microsoft.SqlServer.SqlClrProvider.dll”的错误。修复它(这并不容易)下一个错误将是“Microsoft.SqlServer.BathParser.dll”等找到其他解决方案以确保您的应用程序的灵活性。
      • 这是行不通的,特别是如果其中有一个 alter procedure 语句。它只是抱怨它需要成为批处理中的第一个语句,也就是说,因为在它之前有一个批处理分隔符,但这仍然会引发错误。
      【解决方案7】:

      您可以使用SQL Management Objects 来执行此操作。这些与 Management Studio 用于执行查询的对象相同。我相信Server.ConnectionContext.ExecuteNonQuery() 会满足您的需求。

      【讨论】:

        【解决方案8】:

        我遇到了同样的问题,最终通过简单的字符串替换解决了这个问题,用分号 (;) 替换了单词 GO

        在使用内联 cmets、块 cmets 和 GO 命令执行脚本时,一切似乎都运行良好

        public static bool ExecuteExternalScript(string filePath)
        {
            using (StreamReader file = new StreamReader(filePath))
            using (SqlConnection conn = new SqlConnection(dbConnStr))
            {
                StringBuilder sql = new StringBuilder();
        
                string line;
                while ((line = file.ReadLine()) != null)
                {
                    // replace GO with semi-colon
                    if (line == "GO")
                        sql.Append(";");
                    // remove inline comments
                    else if (line.IndexOf("--") > -1)
                        sql.AppendFormat(" {0} ", line.Split(new string[] { "--" }, StringSplitOptions.None)[0]);
                    // just the line as it is
                    else
                        sql.AppendFormat(" {0} ", line);
                }
                conn.Open();
        
                SqlCommand cmd = new SqlCommand(sql.ToString(), conn);
                cmd.ExecuteNonQuery();
            }
        
            return true;
        }
        

        【讨论】:

        • 它不适用于需要在自己的批处理中的 DDL 命令。例如。创建/更改表等
        • 另外,您似乎无缘无故地删除了 cmets。例如,这会破坏任何包含 -- 的字符串。
        • 嗨 Blorgbeard - SQL Server 2012 似乎可以正常处理 DDL 语句。我使用的脚本允许我重建整个数据库结构,擦除当前结构,构建表,添加索引等。我想;也完成了一批?
        • 此外,删除 cmets 是因为这将产生单行 SQL,因此注释后的任何 SQL 都将被注释掉,但如果有一个字符串包含 - 我同意你的观点不是评论。
        • 啊,好的,刚刚查了一下:“CREATE DEFAULT、CREATE FUNCTION、CREATE PROCEDURE、CREATE RULE、CREATE SCHEMA、CREATE TRIGGER、CREATE VIEW语句不能与其他语句在一个批处理中组合。 CREATE 语句必须启动批处理。该批处理中的所有其他语句将被解释为第一个 CREATE 语句定义的一部分。不能更改表,然后在同一批处理中引用新列。"
        【解决方案9】:

        使用下面的方法来拆分字符串,并逐批执行

        using System;
        using System.IO;
        using System.Text.RegularExpressions;
        namespace RegExTrial
        {
            class Program
            {
                static void Main(string[] args)
                {
                    string sql = String.Empty;
                    string path=@"D:\temp\sample.sql";
                    using (StreamReader reader = new StreamReader(path)) {
                        sql = reader.ReadToEnd();
                    }            
                    //Select any GO (ignore case) that starts with at least 
                    //one white space such as tab, space,new line, verticle tab etc
                    string pattern="[\\s](?i)GO(?-i)";
        
                    Regex matcher = new Regex(pattern, RegexOptions.Compiled);
                    int start = 0;
                    int end = 0;
                    Match batch=matcher.Match(sql);
                    while (batch.Success) {
                        end = batch.Index;
                        string batchQuery = sql.Substring(start, end - start).Trim();
                        //execute the batch
                        ExecuteBatch(batchQuery);
                        start = end + batch.Length;
                        batch = matcher.Match(sql,start);
                    }
        
                }
        
                private static void ExecuteBatch(string command)
                { 
                    //execute your query here
                }
        
            }
        }
        

        【讨论】:

          【解决方案10】:

          如果您不想使用 SMO(这比下面的解决方案更好,但我想提供一个替代方案...)您可以使用此功能拆分您的查询。

          它是:

          • 评论证明(例如 --GO 或 /* GO */)
          • 仅适用于新行,就像在 SSMS 中一样(示例 /* test /* GO 有效并选择 1 作为 go not
          • 字符串证明(例如 print 'no go')

            private List<string> SplitScriptGo(string script)
            {
                var result = new List<string>();
                int pos1 = 0;
                int pos2 = 0;
                bool whiteSpace = true;
                bool emptyLine = true;
                bool inStr = false;
                bool inComment1 = false;
                bool inComment2 = false;
            
                while (true)
                {
                    while (pos2 < script.Length && Char.IsWhiteSpace(script[pos2]))
                    {
                        if (script[pos2] == '\r' || script[pos2] == '\n')
                        {
                            emptyLine = true;
                            inComment1 = false;
                        }
            
                        pos2++;
                    }
            
                    if (pos2 == script.Length)
                        break;
            
                    bool min2 = (pos2 + 1) < script.Length;
                    bool min3 = (pos2 + 2) < script.Length;
            
                    if (!inStr && !inComment2 && min2 && script.Substring(pos2, 2) == "--")
                        inComment1 = true;
            
                    if (!inStr && !inComment1 && min2 && script.Substring(pos2, 2) == "/*")
                        inComment2 = true;
            
                    if (!inComment1 && !inComment2 && script[pos2] == '\'')
                        inStr = !inStr;
            
                    if (!inStr && !inComment1 && !inComment2 && emptyLine
                        && (min2 && script.Substring(pos2, 2).ToLower() == "go")
                        && (!min3 || char.IsWhiteSpace(script[pos2 + 2]) || script.Substring(pos2 + 2, 2) == "--" || script.Substring(pos2 + 2, 2) == "/*"))
                    {
                        if (!whiteSpace)
                            result.Add(script.Substring(pos1, pos2 - pos1));
            
                        whiteSpace = true;
                        emptyLine = false;
                        pos2 += 2;
                        pos1 = pos2;
                    }
                    else
                    {
                        pos2++;
                        whiteSpace = false;
            
                        if (!inComment2)
                            emptyLine = false;
                    }
            
                    if (!inStr && inComment2 && pos2 > 1 && script.Substring(pos2 - 2, 2) == "*/")
                        inComment2 = false;
                }
            
                if (!whiteSpace)
                    result.Add(script.Substring(pos1));
            
                return result;
            }
            

          【讨论】:

            【解决方案11】:

            我在 java 中遇到了同样的问题,我用一些逻辑和正则表达式解决了它。我相信可以应用相同的逻辑。首先我从 slq 文件中读取到内存中。然后我应用以下逻辑。这几乎是之前所说的,但是我相信使用正则表达式单词绑定比期待新行字符更安全。

            String pattern = "\\bGO\\b|\\bgo\\b";
            
            String[] splitedSql = sql.split(pattern);
            for (String chunk : splitedSql) {
              getJdbcTemplate().update(chunk);
            }
            

            这基本上将 sql 字符串拆分为一个 sql 字符串数组。正则表达式基本上是检测小写或大写的完整“go”词。然后依次执行不同的查询。

            【讨论】:

            • 小心:你将如何分割这个? insert into books values ('1478355824', 'An Introduction To Programming in Go (paperback)', 9.00)
            • 好点 :-) 我的情况没有插入数据。只是创建表、存储过程和函数。在我的特殊情况下,bound 这个词更有用,因为它还处理了最后一行的“go”。
            【解决方案12】:

            基于 Blorgbeard 的解决方案。

            foreach (var sqlBatch in commandText.Split(new[] { "GO" }, StringSplitOptions.RemoveEmptyEntries))
            {
               sqlCommand.CommandText = sqlBatch;
               sqlCommand.ExecuteNonQuery();
            }
            

            【讨论】:

            • new[] { "GO", "Go", "go" }
            • new[] { "GO", "Go", "go", "gO" }
            • 只要您对代码中的两个字母没有其他用途,例如 GOTO-Statements 或 cmets,就可以使用。
            • new[] { "GO --DELIMITER" }
            【解决方案13】:

            我今天通过将我的 SQL 从文本文件加载到一个字符串中来实现这一点。然后我使用字符串拆分函数将字符串分成单独的命令,然后单独发送到服务器。简单的:)

            刚刚意识到您需要在 \nGO 上进行拆分,以防万一字母 GO 出现在您的任何表名等中。我猜我很幸运!

            【讨论】:

              【解决方案14】:

              太难了:)

              创建字符串数组 str[] 将 GO 替换为 ",@" :

                          string[] str ={
                              @"
              USE master;
              ",@"
              
              
              CREATE DATABASE " +con_str_initdir+ @";
              ",@"
              -- Verify the database files and sizes
              --SELECT name, size, size*1.0/128 AS [Size in MBs] 
              --SELECT name 
              --FROM sys.master_files
              --WHERE name = N'" + con_str_initdir + @"';
              --GO
              
              USE " + con_str_initdir + @";
              ",@"
              
              SET ANSI_NULLS ON
              ",@"
              SET QUOTED_IDENTIFIER ON
              ",@"
              
              IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Customers]') AND type in (N'U'))
              BEGIN
              CREATE TABLE [dbo].[Customers](
                  [CustomerID] [int] IDENTITY(1,1) NOT NULL,
                  [CustomerName] [nvarchar](50) NULL,
               CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED 
              (
                  [CustomerID] ASC
              )WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
              ) ON [PRIMARY]
              END
              ",@"
              
              
              
              SET ANSI_NULLS ON
              ",@"
              SET QUOTED_IDENTIFIER ON
              ",@"
              IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GOODS]') AND type in (N'U'))
              BEGIN
              CREATE TABLE [dbo].[GOODS](
                  [GoodsID] [int] IDENTITY(1,1) NOT NULL,
                  [GoodsName] [nvarchar](50) NOT NULL,
                  [GoodsPrice] [float] NOT NULL,
               CONSTRAINT [PK_GOODS] PRIMARY KEY CLUSTERED 
              (
                  [GoodsID] ASC
              )WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
              ) ON [PRIMARY]
              END
              ",@"
              SET ANSI_NULLS ON
              ",@"
              SET QUOTED_IDENTIFIER ON
              ",@"
              IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Orders]') AND type in (N'U'))
              BEGIN
              CREATE TABLE [dbo].[Orders](
                  [OrderID] [int] IDENTITY(1,1) NOT NULL,
                  [CustomerID] [int] NOT NULL,
                  [Date] [smalldatetime] NOT NULL,
               CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED 
              (
                  [OrderID] ASC
              )WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
              ) ON [PRIMARY]
              END
              ",@"
              SET ANSI_NULLS ON
              ",@"
              SET QUOTED_IDENTIFIER ON
              ",@"
              IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[OrderDetails]') AND type in (N'U'))
              BEGIN
              CREATE TABLE [dbo].[OrderDetails](
                  [OrderID] [int] NOT NULL,
                  [GoodsID] [int] NOT NULL,
                  [Qty] [int] NOT NULL,
                  [Price] [float] NOT NULL,
               CONSTRAINT [PK_OrderDetails] PRIMARY KEY CLUSTERED 
              (
                  [OrderID] ASC,
                  [GoodsID] ASC
              )WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
              ) ON [PRIMARY]
              END
              ",@"
              
              SET ANSI_NULLS ON
              ",@"
              SET QUOTED_IDENTIFIER ON
              ",@"
              IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[InsertCustomers]') AND type in (N'P', N'PC'))
              BEGIN
              EXEC dbo.sp_executesql @statement = N'-- =============================================
              -- Author:      <Author,,Name>
              -- Create date: <Create Date,,>
              -- Description: <Description,,>
              -- =============================================
              create PROCEDURE [dbo].[InsertCustomers]
               @CustomerName nvarchar(50),
               @Identity int OUT
              AS
              INSERT INTO Customers (CustomerName) VALUES(@CustomerName)
              SET @Identity = SCOPE_IDENTITY()
              
              ' 
              END
              ",@"
              IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_Orders_Customers]') AND parent_object_id = OBJECT_ID(N'[dbo].[Orders]'))
              ALTER TABLE [dbo].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([CustomerID])
              REFERENCES [dbo].[Customers] ([CustomerID])
              ON UPDATE CASCADE
              ",@"
              ALTER TABLE [dbo].[Orders] CHECK CONSTRAINT [FK_Orders_Customers]
              ",@"
              IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_OrderDetails_GOODS]') AND parent_object_id = OBJECT_ID(N'[dbo].[OrderDetails]'))
              ALTER TABLE [dbo].[OrderDetails]  WITH CHECK ADD  CONSTRAINT [FK_OrderDetails_GOODS] FOREIGN KEY([GoodsID])
              REFERENCES [dbo].[GOODS] ([GoodsID])
              ON UPDATE CASCADE
              ",@"
              ALTER TABLE [dbo].[OrderDetails] CHECK CONSTRAINT [FK_OrderDetails_GOODS]
              ",@"
              IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_OrderDetails_Orders]') AND parent_object_id = OBJECT_ID(N'[dbo].[OrderDetails]'))
              ALTER TABLE [dbo].[OrderDetails]  WITH CHECK ADD  CONSTRAINT [FK_OrderDetails_Orders] FOREIGN KEY([OrderID])
              REFERENCES [dbo].[Orders] ([OrderID])
              ON UPDATE CASCADE
              ON DELETE CASCADE
              ",@"
              ALTER TABLE [dbo].[OrderDetails] CHECK CONSTRAINT [FK_OrderDetails_Orders]
              
              
                              "};
              
              
                          for(int i =0; i<str.Length;i++)     
                          {
                              myCommand.CommandText=str[i];
                              try
                              {
                              myCommand.ExecuteNonQuery();
                              }
                              catch (SystemException ee)
                              {
                                  MessageBox.Show("Error   "+ee.ToString());
                              }
              
                          }
              

              就是这样,享受吧。

              【讨论】:

                【解决方案15】:

                如果您不想安装 SMO 对象,可以使用 gplex 工具(请参阅this answer

                【讨论】:

                  【解决方案16】:

                  如果您不想走 SMO 路线,您可以搜索“GO”并将“;”替换为“;”和你想要的查询。请注意,只会返回最后一个结果集。

                  【讨论】:

                  • 他们正在执行 ExecuteNonQuery。这是迄今为止更简单的方法。
                  • 使用“GO”将允许您在批处理的下一个命令中再次定义相同的变量。放置分号不会这样做。
                  【解决方案17】:

                  “GO”批处理分隔符关键字实际上是由 SQL Management Studio 自己使用的,因此它知道在哪里终止它正在发送到服务器的批处理,而不是传递给 SQL 服务器。如果您愿意,您甚至可以在 Management Studio 中更改关键字。

                  【讨论】:

                    【解决方案18】:

                    这是我为了解决眼前的问题而拼凑起来的。

                    private void ExecuteBatchNonQuery(string sql, SqlConnection conn) {
                        string sqlBatch = string.Empty;
                        SqlCommand cmd = new SqlCommand(string.Empty, conn);
                        conn.Open();
                        sql += "\nGO";   // make sure last batch is executed.
                        try {
                            foreach (string line in sql.Split(new string[2] { "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries)) {
                                if (line.ToUpperInvariant().Trim() == "GO") {
                                    cmd.CommandText = sqlBatch;
                                    cmd.ExecuteNonQuery();
                                    sqlBatch = string.Empty;
                                } else {
                                    sqlBatch += line + "\n";
                                }
                            }            
                        } finally {
                            conn.Close();
                        }
                    }
                    

                    它要求GO命令在自己的一行,并且不会检测block-cmets,所以这种事情会被拆分,并导致错误:

                    ExecuteBatchNonQuery(@"
                        /*
                        GO
                        */", conn);
                    

                    【讨论】:

                    • 如果需要的话,我可以很容易地将它适配到 SqlCe,这很好——其他代码使用 Sql 连接类和命令。
                    • 我想用一个包含多个存储过程的 SQL 脚本运行这段代码,但我有点困惑,它在哪里读取 SQL?当您提到“最后一批”时,您是指 SQL 代码吗?如果是这样,您将如何确定最后一批,如果我想运行所有批次而不仅仅是最后一批怎么办?我知道的问题太多,但如果你有时间回答,谢谢。
                    • 您将 SQL 作为字符串传递给函数:string sql - 这就是整个脚本。当我提到“批处理”时,我指的是两个“GO”语句之间的一段 SQL 代码。该代码将GO 添加到脚本的末尾,以便foreach 中的代码在您没有以GO 结束脚本时不会跳过最后一批。因此编写的代码将执行所有 SQL。
                    • 我创建了一个扩展方法:internal static class SqlCommandHelper { internal static void ExecuteBatchNonQuery(this SqlCommand cmd, string sql)
                    • 如果你想提高一点效率,你可以改用StringBuilder sqlBatch
                    【解决方案19】:

                    我也遇到了同样的问题,除了将单个 SQL 操作拆分到单独的文件中,然后依次执行所有这些操作之外,我找不到其他方法。

                    显然问题不在于 DML 命令列表,它们可以在没有 GO 的情况下执行; DDL 的不同故事(创建、更改、删​​除...)

                    【讨论】:

                      猜你喜欢
                      • 2011-10-07
                      • 2012-10-20
                      • 1970-01-01
                      • 1970-01-01
                      • 2020-07-18
                      • 2017-01-13
                      • 1970-01-01
                      • 2011-12-22
                      相关资源
                      最近更新 更多