I\'m building a custom db deployment utility, I need to read text files containing sql scripts and execute them against the database.
Pretty easy stuff, so far so g
Answer based on comments under the original post:
GO is a marker for Management Studio / osql / isql. It tells to send a batch of commands to SQL Server. In your utility, you should split the input data using GO as a delimiter and send each element individually (without the GO command)
This is what we use :)
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
}
Yep, everyone hits this snag the first time they start sending the contents of SQL script files to the database.
GO
is not a T-SQL command. It's the end-of-batch marker recognised by all the Microsoft interactive SQL tools (Management Studio, isql, osql). In order to handle it, you will have to write your own parser to break out every block of text in the file between GO
statements and feed them to the database as separate commands.
How you implement your parser is up to you. It could be simple (read in each line at a time, detect lines that consist of nothing but GO
and whitespace) or complex (tokenising all the statements and working out whether a GO
is a genuine statement or a bit of text inside a string or multi-line comment).
Personally I went with the first option. It handles 99% of all SQL files you are ever likely to encounter with no fuss. If you want to go the whole hog and write a tokeniser, I'm sure lots of people have done one already, just Google for it.
Example:
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.
}
}
I found the code below while searching for an answer to this issue:
http://blogs.msdn.com/b/onoj/archive/2008/02/26/incorrect-syntax-near-go-sqlcommand-executenonquery.aspx
Pros: It's short and simple to understand and worked perfectly for my needs.
Cons: It is less efficient than Stream based solutions and is case sensitive (i.e "GO" not "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();
}
ExecuteNonQuery in SMO works with batches:
http://msdn.microsoft.com/en-us/library/microsoft.sqlserver.management.smo.database.executenonquery.aspx
I ended up writing an implementation of a StringReader to do this.
It handles:
Therefore it will only detect the keyword GO when used as a batch seperator. This means it splits the SQL text correctly.
It also handles if you have appended a sql terminator (semicolon) to the word GO
You can find the code for it here:
You use it like so:
using (var reader = new SqlCommandReader(scriptContents))
{
var commands = new List<string>();
reader.ReadAllCommands(c => commands.Add(c));
// commands now contains each seperated sql batch.
}