Выполнить большой сценарий SQL (с командами GO)


89

Мне нужно выполнить большой набор операторов SQL (создание группы таблиц, представлений и хранимых процедур) из программы на C #.

Эти операторы нужно разделять GOоператорами, но SqlCommand.ExecuteNonQuery()это не нравится GOоператорам. Мое решение, которое, я полагаю, я опубликую для справки, заключалось в том, чтобы разделить строку SQL на GOстроки и выполнить каждый пакет отдельно.

Есть ли способ проще / лучше?

Ответы:


107

Используйте объекты управления SQL Server (SMO), которые понимают разделители GO. См. Мое сообщение в блоге здесь: 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);
        }
   }
}

Если это не сработает для вас, см. Библиотеку Фила Хаака, которая справляется с этим: http://haacked.com/archive/2007/11/04/a-library-for-executing-sql-scripts-with-go-separators -and.aspx


2
Как это можно интегрировать с транзакцией? Код выдает InvalidOperationException при создании ServerConnection с SqlConnection, на котором есть ожидающая транзакция.
benPearce

1
Это решение работает, я просто хочу добавить, что если вы хотите использовать транзакции с TransactionScopeобъектом, вам просто нужно подключить соединение с текущей внешней транзакцией. Проверьте мой ответ здесь: stackoverflow.com/a/18322938/1268570
Jupaol

отлично работает, но можем ли мы использовать SqlConnection.InfoMessage), чтобы увидеть результат в приложении C # или сохранить результат в txtфайле, просто чтобы знать, был ли сценарий успешно выполнен, потому что недавно использовал, sqlcmdкогда я выполнил файл сценария 150 МБ на удаленном хосте, через 55 минут некоторые строки были осуществлены с этой ошибкой, TCP Provider: An existing connection was forcibly closed by the remote host., communication link failure. , невозможно узнать ни одну из обработанных строк, но меня беспокоят сообщения об ошибках при запуске файла сценария, созданного базой данных.
Shaijut

5
Это решение привело к сбою вашего кода, если на компьютере не установлены некоторые библиотеки DLL SQL. .NET использует некоторые DLL, встроенные в Windows. Отсутствие некоторых пакетов функций SQL (в том числе объектов управления) может предотвратить появление таких ошибок, как «Microsoft.SqlServer.SqlClrProvider.dll не найден». Исправив это (это непростая работа), следующей ошибкой будет Microsoft.SqlServer.BathParser.dll и т. Д. Найдите другое решение для обеспечения гибкости вашего приложения.
Александр Саргсян

Это не работает, особенно если там есть оператор изменения процедуры. Он просто жалуется, что это должен быть первый оператор в пакете, потому что прямо перед ним стоит разделитель пакетов, но это все равно вызывает ошибку.
Трийнко

35

Это то, что я собрал, чтобы решить мою непосредственную проблему.

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 находились в отдельной строке и не будут обнаруживать блочные комментарии, поэтому такие вещи будут разделены и вызовут ошибку:

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

Приятно, что при необходимости я могу легко адаптировать это к SqlCe - другой код использует классы и команды подключения Sql.
Blue Toque

Я хочу запустить этот код с помощью сценария SQL с несколькими хранимыми процедурами внутри, но я немного запутался, где он читает SQL? Когда вы говорите о «последней партии», вы имеете в виду код SQL? И если да, то как бы вы определяли последний пакет, и что, если бы я хотел запустить все пакеты, а не только последний? Я знаю слишком много вопросов, но спасибо, если у вас есть время ответить.
user1676874

Вы передаете SQL в функцию в виде строки: string sql- это весь сценарий. Когда я говорю о «пакете», я имею в виду фрагмент кода SQL между двумя операторами «GO». Код добавляет GOв конец скрипта, чтобы код внутри foreachне пропустил последний пакет, если вы не завершили свой скрипт расширением GO. Таким образом, написанный код будет выполнять весь SQL.
Blorgbeard выйдет

Я создал метод расширения: внутренний статический класс SqlCommandHelper {internal static void ExecuteBatchNonQuery (this SqlCommand cmd, string sql)
Роб Седжвик

1
Если вы хотите быть немного более эффективным, вы можете использовать StringBuilder sqlBatchвместо этого.
Lii

11

Для этого можно использовать объекты управления SQL . Это те же объекты, которые Management Studio использует для выполнения запросов. Я верю Server.ConnectionContext.ExecuteNonQuery()выполню то, что вам нужно.


6

Ключевое слово разделителя пакетов «GO» фактически используется самой SQL Management Studio, поэтому оно знает, где завершить пакеты, которые оно отправляет на сервер, и не передается на сервер SQL. Вы даже можете изменить ключевое слово в Management Studio, если захотите.


6

Я смотрел на это несколько раз, в конце решил, что реализация EF Немного изменена дляSqlConnection

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

2
@DipakRiswadkar Да, заблокировал этот вопрос несколько раз, и ни один из предоставленных ответов не соответствует моим потребностям, поэтому посмотрел, как реализация EF выглядела хорошо, поэтому я опубликовал ответ.
Филип Кордас

Потрясающий ответ, работает как шарм, большое спасибо
cuongle

@Really также должен сообщить об этом команде Entity Framework. Как я уже сказал, это всего лишь копия прошлого с небольшими изменениями.
Филип Кордас


4

На основе решения Боргберда.

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

3
new [] {"GO", "Go", "go"}
Эндрю Верига,

2
new [] {"GO", "Go", "go", "gO"}
Брэндон Уорд

Работает до тех пор, пока у вас нет другого использования для двух букв в вашем коде, например GOTO-Statements или комментариев.
Patrik

3

Если вы не хотите использовать SMO, например, потому что вам нужно быть кроссплатформенным, вы также можете использовать класс ScriptSplitter из SubText.

Вот реализация на 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, удалите комментарии с помощью регулярного выражения:

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

Удаление однострочных комментариев здесь:

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

этот класс рассматривает /* Go */случай?
edgarmtze

@cMinor: не в разделителе, но вы можете удалить многострочные комментарии с помощью регулярного выражения перед разделением.
Стефан Штайгер

2

Я также столкнулся с той же проблемой, и я не мог найти другого способа, кроме разделения одной операции SQL на отдельные файлы, а затем их последовательного выполнения.

Очевидно, проблема не в списках команд DML, они могут выполняться без перехода между ними; другая история с DDL (создать, изменить, отбросить ...)


2

Если вы не хотите идти по маршруту SMO, вы можете найти и заменить «GO» на «;» и запрос, как и вы. Обратите внимание, что будет возвращен последний набор результатов.


1
Они выполняют ExecuteNonQuery. Это намного более простой способ.
Дэйв Морган, Техас, 01

3
Использование «GO» позволит вам снова определить те же переменные в следующей команде в пакете. Если поставить точку с запятой, этого не произойдет.
DDRider62 08

2

Я сделал это сегодня, загрузив свой SQL из текстового файла в одну строку. Затем я использовал функцию разделения строки, чтобы разделить строку на отдельные команды, которые затем отправлялись на сервер по отдельности. Просто :)

Просто понял, что вам нужно разделить \ nGO на случай, если буквы GO появятся в любом из ваших имен таблиц и т. Д. Думаю, мне там повезло!


2

Если вы не хотите использовать SMO (что лучше, чем решение ниже, но я хочу дать альтернативу ...), вы можете разделить свой запрос с помощью этой функции.

Это:

  • Подтверждение комментария (пример --GO или / * GO * /)
  • Работает только с новой строкой, как и в SSMS (пример / * test / * GO работает и выберите 1 как go not
  • Строковое доказательство (пример печати '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;
    }
    

1

используйте следующий метод, чтобы разделить строку и выполнить пакет за пакетом

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
        }

    }
}

1

Чтобы избежать посторонних, регулярных выражений, накладных расходов памяти и быстрой работы с большими скриптами, я создал свой собственный потоковый парсер. Это

  • проверяет синтаксис перед
  • может распознавать комментарии с помощью - или / ** /

    -- some commented text
     /*
    drop table Users;
    GO
       */
    
  • может распознавать строковые литералы с 'или "

    set @s =
        'create table foo(...);
        GO
        create index ...';
    
  • сохраняет форматирование LF и CR
  • сохраняет блок комментариев в телах объектов (хранимых процедурах, представлениях и т. д.)
  • и другие конструкции, такие как

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

0

У меня была такая же проблема в java, и я решил ее с помощью логики и регулярного выражения. Я считаю, что можно применить ту же логику. Сначала я читаю из файла slq в память. Затем применяю следующую логику. Это в значительной степени то, что было сказано ранее, однако я считаю, что использование регулярного выражения с привязкой к слову безопаснее, чем ожидание новой строки char.

String pattern = "\\bGO\\b|\\bgo\\b";

String[] splitedSql = sql.split(pattern);
for (String chunk : splitedSql) {
  getJdbcTemplate().update(chunk);
}

Это в основном разбивает строку sql на массив строк sql. Регулярное выражение в основном предназначено для обнаружения полных «идущих» слов в нижнем или верхнем регистре. Затем вы последовательно выполняете разные запросы.


1
Осторожно: как вы это разделите? insert into books values ('1478355824', 'An Introduction To Programming in Go (paperback)', 9.00)
Blorgbeard отсутствует

Хороший момент :-) В моей ситуации не было вставок данных. Просто создавал таблицы, хранимые процедуры и функции. Связка слов была более полезной в моем конкретном случае, потому что она также учитывала «идти» в последней строке.
jbrunodomingues

0

Я столкнулся с той же проблемой и в конце концов решил ее простой заменой строки, заменив слово GO точкой с запятой (;)

Кажется, все работает нормально при выполнении сценариев со встроенными комментариями, комментариями блока и командами 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;
}

1
Это не сработает для команд DDL, которые должны быть в отдельном пакете. Например, create / alter table и др.
Blorgbeard выйдет

Кроме того, похоже, что вы удаляете комментарии без всякой причины .. Что, например, нарушит любые строки, содержащие --.
Blorgbeard выйдет

Привет, Blorgbeard - SQL Server 2012, похоже, нормально обрабатывает операторы DDL. Скрипты, которые я использовал, должны были позволить мне перестроить всю структуру базы данных, стереть текущую структуру, построить таблицы, добавить индексы и т. Д. Я подумал; тоже закончил партию?
Морваэль

Кроме того, удаление комментариев было вызвано тем, что это приведет к созданию одной строки SQL, поэтому любой SQL после комментария будет закомментирован, но я понимаю вашу точку зрения, если там была строка, которая содержала - это не был комментарий.
Морваэль

1
Хорошо, только что посмотрел: «Операторы CREATE DEFAULT, CREATE FUNCTION, CREATE PROCEDURE, CREATE RULE, CREATE SCHEMA, CREATE TRIGGER и CREATE VIEW нельзя комбинировать с другими операторами в пакете. Операторы CREATE должны запускать пакет. Все». другие операторы, следующие в этом пакете, будут интерпретироваться как часть определения первого оператора CREATE. Невозможно изменить таблицу, а затем новые столбцы, указанные в том же пакете ».
Blorgbeard выйдет

-1

Для тех, у кого все еще есть проблема. Вы можете использовать официальный Microsoft 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 лет назад!).
Blorgbeard отсутствует

-4

Слишком сложно :)

Создайте массив строк 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());
                }

            }

Вот и все, наслаждайтесь.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.