Вопрос

Я создаю пользовательскую утилиту развертывания БД, мне нужно прочитать текстовые файлы, содержащие sql-скрипты, и выполнить их в базе данных.

Довольно простая штука, пока что очень хорошая.

Однако я столкнулся с проблемой: содержимое файла считывается успешно и полностью, но после передачи в SqlCommand, а затем выполняется с помощью SqlCommand.ExecuteNonQuery выполняется только часть скрипта.

Я запустил Profiler и подтвердил, что мой код передает не весь скрипт.

    private void ExecuteScript(string cmd, SqlConnection sqlConn, SqlTransaction trans)
    {

        SqlCommand sqlCmd = new SqlCommand(cmd, sqlConn, trans);
        sqlCmd.CommandType = CommandType.Text;
        sqlCmd.CommandTimeout = 9000000; // for testing
        sqlCmd.ExecuteNonQuery();

    }

    // I call it like this, readDMLScript contains 543 lines of T-SQL
    string readDMLScript = ReadFile(dmlFile);
    ExecuteScript(readDMLScript, sqlConn, trans);
Это было полезно?

Решение

Да, все сталкиваются с этой проблемой при первой отправке содержимого файлов SQL-скриптов в базу данных.

GO это не команда T-SQL.Это маркер конца пакета, распознаваемый всеми интерактивными инструментами Microsoft SQL (Management Studio, isql, osql).Чтобы справиться с этим, вам придется написать свой собственный синтаксический анализатор, чтобы выделить каждый блок текста в файле между GO операторы и передают их в базу данных в виде отдельных команд.

То, как вы реализуете свой синтаксический анализатор, зависит от вас.Это могло бы быть просто (читать по каждой строке за раз, обнаруживать строки, которые состоят только из GO и пробелы) или сложный (обозначающий все операторы и определяющий, является ли GO является подлинным утверждением или фрагментом текста внутри строки или многострочного комментария).

Лично я выбрал первый вариант.Он обрабатывает 99% всех SQL-файлов, с которыми вы, вероятно, когда-либо столкнетесь, без лишней суеты.Если вы хотите пойти до конца и написать токенизатор, я уверен, что многие люди уже сделали это, просто загуглите для этого.

Пример:

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

Другие советы

Я нашел приведенный ниже код во время поиска ответа на этот вопрос:

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

Плюсы:Он короткий и простой для понимания и идеально подходит для моих нужд.

Минусы:Это менее эффективно, чем потоковые решения, и чувствительно к регистру (т.е. "GO", а не "перейти").

string[] commands = sql.Split(new string[]{"GO\r\n", "GO ", "GO\t"}, StringSplitOptions.RemoveEmptyEntries );
foreach (string c in commands)
{
    var command = new SqlCommand(c, masterConnection);
    command.ExecuteNonQuery();
}

Ответ основан на комментариях под оригинальным сообщением:

GO - это маркер для Management Studio / osql / isql.Он сообщает отправить пакет команд на SQL Server.В вашей утилите вы должны разделить входные данные, используя GO в качестве разделителя, и отправить каждый элемент по отдельности (без команды GO).

Это то, что мы используем :)

public static class ExtensionMethodsSqlCommand
{
    #region Public

    private static bool IsGo(string psCommandLine)
    {
        if (psCommandLine == null)
            return false;
        psCommandLine = psCommandLine.Trim();
        if (string.Compare(psCommandLine, "GO", StringComparison.OrdinalIgnoreCase) == 0)
            return true;
        if (psCommandLine.StartsWith("GO", StringComparison.OrdinalIgnoreCase))
        {
            psCommandLine = (psCommandLine + "--").Substring(2).Trim();
            if (psCommandLine.StartsWith("--"))
                return true;
        }
        return false;
    }

    [System.Diagnostics.DebuggerHidden]
    public static void ExecuteNonQueryWithGos(this SqlCommand poSqlCommand)
    {
        string sCommandLong = poSqlCommand.CommandText;
        using (StringReader oStringReader = new StringReader(sCommandLong))
        {
            string sCommandLine;
            string sCommandShort = string.Empty;
            while ((sCommandLine = oStringReader.ReadLine()) != null)
                if (ExtensionMethodsSqlCommand.IsGo(sCommandLine))
                {
                    if (sCommandShort.IsNullOrWhiteSpace() == false)
                    {
                        if ((poSqlCommand.Connection.State & ConnectionState.Open) == 0)
                            poSqlCommand.Connection.Open();
                        using (SqlCommand oSqlCommand = new SqlCommand(sCommandShort, poSqlCommand.Connection))
                            oSqlCommand.ExecuteNonQuery();
                    }
                    sCommandShort = string.Empty;
                }
                else
                    sCommandShort += sCommandLine + "\r\n";
        }
    }

    #endregion Public
}

В итоге я написал реализацию StringReader для этого.

Он обрабатывает:

  1. Пропуск мимо ПЕРЕХОДА, содержащегося в комментариях к dash dash
  2. Пропуск мимо перехода, содержащегося в комментариях к косой черте звезды
  3. Пропуск мимо перехода, содержащегося в литералах (т.е. одинарных кавычках)
  4. Пропуск мимо перехода, содержащегося в названиях столбцов и т.д.

Поэтому он обнаружит ключевое слово GO только при использовании в качестве разделителя пакетов.Это означает, что он правильно разделяет текст SQL.

Он также обрабатывает, добавили ли вы sql-терминатор (точку с запятой) к слову GO

Вы можете найти код для этого здесь:

Вы используете это вот так:

using (var reader = new SqlCommandReader(scriptContents))
       {
            var commands = new List<string>();
            reader.ReadAllCommands(c => commands.Add(c));
            // commands now contains each seperated sql batch.
        }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top