SqlCommand() ExecuteNonQuery() обрезает текст команды

Я создаю пользовательскую утилиту развертывания базы данных, мне нужно читать текстовые файлы, содержащие сценарии 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);

person H. Abraham Chavez    schedule 16.03.2010    source источник
comment
До какого символа усекается сценарий?   -  person MikeWyatt    schedule 16.03.2010
comment
как работает метод ReadFile? Вы на 200% уверены, что это не пропуск нескольких символов, может быть?? Почему бы просто не использовать System.IO.File.ReadAllText(filename) ??   -  person marc_s    schedule 16.03.2010
comment
сколько сколько текста вы читаете из файла, в байтах?   -  person marc_s    schedule 16.03.2010
comment
Спасибо за помощь, Марк, я использовал программу для чтения Stream. Я переключусь на System.IO.File.ReadAllText(имя файла) для ясности. Теперь о проблеме, я подозревал, что сценарий был испорчен, потому что при его выполнении с использованием исключений SqlCommand, связанных с синтаксисом сценария, возникали, когда он нормально работал в Sql Management Studio. Похоже, у меня сегодня утром была какая-то травма головы, оказывается, сценарий не урезан.   -  person H. Abraham Chavez    schedule 16.03.2010
comment
Исключения все еще сдерживают меня. Знаете ли вы, почему что-то вроде этого: System.Data.SqlClient.SqlException: неправильный синтаксис рядом с «GO». Неверный синтаксис рядом с «GO». Неверный синтаксис рядом с «GO». Будет ли отображаться в SqlCommand, но не в Sql Management Studio при выполнении скрипта?   -  person H. Abraham Chavez    schedule 16.03.2010


Ответы (6)


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

GO не является командой T-SQL. Это маркер конца пакета, распознаваемый всеми интерактивными инструментами SQL Microsoft (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.
    }
}
person Christian Hayter    schedule 16.03.2010
comment
Если только ваш файл сценария не начинается с «SET IDENTITY_INSERT ‹table_name› ON». Нет ли хорошего решения для экспорта данных в файл для .NET для последующего импорта? - person MStodd; 29.05.2015
comment
@MStodd: SET IDENTITY_INSERT предназначен для использования в сценариях импорта данных, поэтому это хорошее решение. Однако лично я бы использовал SSIS для импорта данных. - person Christian Hayter; 01.06.2015
comment
Проблема, с которой я столкнулся сейчас, заключается в том, что если я экспортирую скрипт из SSMS, начиная с «SET IDENTITY_INSERT ‹table_name› ON», этого будет недостаточно, чтобы использовать ваш код, не так ли? Разве мне не нужно будет выполнять эту команду перед каждой вставкой? - person MStodd; 01.06.2015
comment
@MStodd: SET IDENTITY_INSERT остается установленным, пока вы его не выключите, вам нужно установить его только один раз. Какая именно проблема у вас возникла с вашим сценарием? - person Christian Hayter; 02.06.2015
comment
У меня возникла эта проблема: stackoverflow.com/questions/7714812/ - person MStodd; 02.06.2015
comment
@MStodd: понятно. Однако это будет проблемой только в том случае, если соединение сбрасывается между командами. Если вы оставите его открытым, как в моем коде выше или в SSIS, он должен работать нормально. - person Christian Hayter; 03.06.2015

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

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

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

Минусы: он менее эффективен, чем решения на основе Stream, и чувствителен к регистру (т. Е. «GO», а не «go»).

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

ExecuteNonQuery в SMO работает с партиями:

http://msdn.microsoft.com/en-us/library/microsoft.sqlserver.management.smo.database.executenonquery.aspx

person Adam Butler    schedule 12.01.2012
comment
Теперь, когда я использовал библиотеку SMO, я бы посоветовал отказаться от нее и порекомендовать одно из чистых решений .net. - person Adam Butler; 01.11.2013

Ответ на основе комментариев под исходным постом:

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

person Timores    schedule 16.03.2010

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

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
}
person user2606283    schedule 22.07.2013
comment
Почему бы вам не добавить описание примера кода, который вы опубликовали? Легче прочитать описание и понять, работает решение или нет, чем потратить некоторое время на анализ того, что делает код. - person Artemix; 22.07.2013

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

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

  1. Пропуск последних GO, содержащихся в комментариях тире тире
  2. Пропуск прошлого GO, содержащегося в комментариях с косой чертой
  3. Пропуск прошлого GO, содержащегося в литералах (т.е. одинарных кавычках)
  4. Пропуск последних GO, содержащихся в именах столбцов и т. д.

Поэтому он будет обнаруживать ключевое слово 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.
        }
person Darrell    schedule 26.02.2015