SqlCommand() ExecuteNonQuery() обрезает текст команды
Я создаю специальную утилиту для развертывания db, мне нужно прочитать текстовые файлы, содержащие sql-скрипты, и выполнить их в базе данных.
Довольно легкий материал, настолько хороший.
Однако я столкнулся с проблемой, содержимое файла прочитано успешно и полностью, но после его передачи в SqlCommand и затем выполняется с помощью SqlCommand.ExecuteNonQuery выполняется только часть script.
Я активировал Profiler и подтвердил, что мой код не передает все script.
private void ExecuteScript(string cmd, SqlConnection sqlConn, SqlTransaction trans)
{
SqlCommand sqlCmd = new SqlCommand(cmd, sqlConn, trans);
sqlCmd.CommandType = CommandType.Text;
sqlCmd.CommandTimeout = 9000000; // for testing
sqlCmd.ExecuteNonQuery();
}
// I call it like this, readDMLScript contains 543 lines of T-SQL
string readDMLScript = ReadFile(dmlFile);
ExecuteScript(readDMLScript, sqlConn, trans);
Ответы
Ответ 1
Да, каждый попадает в эту ловушку при первом запуске отправки содержимого файлов SQL script в базу данных.
GO
не является командой T-SQL. Это маркер конца пакета, распознаваемый всеми интерактивными инструментами SQL Microsoft (Management Studio, isql, osql). Чтобы справиться с этим, вам придется написать собственный синтаксический анализатор, чтобы разбить каждый блок текста в файле между операторами GO
и передать их в базу данных в виде отдельных команд.
Как вы реализуете свой парсер, зависит от вас. Это может быть просто (читать в каждой строке за раз, обнаруживать строки, состоящие из ничего, кроме GO
и пробелов) или сложных (токенизация всех операторов и разработка того, является ли GO
подлинным выражением или немного текста внутри строки или многострочного комментария).
Лично я пошел с первым вариантом. Он обрабатывает 99% всех файлов SQL, с которыми вы когда-либо столкнетесь, без суеты. Если вы хотите отправиться на целый боров и написать токенизер, я уверен, что многие люди уже сделали это, просто Google для него.
Пример:
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.
}
}
Ответ 2
Я нашел код ниже при поиске ответа на эту проблему:
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();
}
Ответ 3
ExecuteNonQuery в SMO работает с партиями:
http://msdn.microsoft.com/en-us/library/microsoft.sqlserver.management.smo.database.executenonquery.aspx
Ответ 4
Ответ основан на комментариях по исходному сообщению:
GO является маркером для Management Studio/osql/isql. Он сообщает отправить пакет команд SQL Server. В вашей утилите вы должны разделить входные данные с помощью GO в качестве разделителя и отправить каждый элемент отдельно (без команды GO)
Ответ 5
Это то, что мы используем:)
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
}
Ответ 6
В итоге я написал реализацию StringReader для этого.
Он обрабатывает:
- Пропуск последнего GO, содержащийся в комментариях тире
- Пропуск последнего GO, содержащегося в комментариях слэш-слайдов
- Пропуск прошлого GO, содержащийся в литералах (например, одинарные кавычки)
- Пропуск последнего 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.
}