ADO.NET and ExecuteNonQuery: how to use DDL

2019-09-14 19:53发布

问题:

I execute SQL scripts to change the database schema. It looks something like this:

using (var command = connection.CreateCommand())
{
    command.CommandText = script;
    command.ExecuteNonQuery();
}

Additionally, the commands are executed within a transaction.

The scrip looks like this:

Alter Table [TableName]
ADD [NewColumn] bigint NULL

Update [TableName]
SET [NewColumn] = (SELECT somevalue FROM anothertable)

I get an error, because NewColumn does not exist. It seems to parse and validate it before it is executed.

When I execute the whole stuff in the Management Studio, I can put GO between the statements, then it works. When I put GO into the script, ADO.NET complains (Incorrect syntax near 'GO').

I could split the script into separate scripts and execute it in separate commands, this would be hard to handle. I could split it on every GO, parsing the script myself. I just think that there should be a better solution and that I didn't understand something. How should scripts like this be executed?


My implementation if anyone is interested in, according to John Saunders' answer:

List<string> lines = new List<string>();
while (!textStreamReader.EndOfStream)
{
    string line = textStreamReader.ReadLine();
    if (line.Trim().ToLower() == "go" || textStreamReader.EndOfStream)
    {
        ExecuteCommand(
            string.Join(Environment.NewLine, lines.ToArray()));

        lines.Clear();
    }
    else
    {
        lines.Add(line);
    }
}

回答1:

You have to run each batch separately. In particular, to run a script that may contain multiple batches ("GO" keywords), you have to split the script on the "GO" keywords.

Not Tested:

string script = File.ReadAllText("script.sql");
string[] batches = script.Split(new [] {"GO"+Environment.NewLine}, StringSplitOptions.None);
foreach (string batch in batches)
{
    // run ExecuteNonQuery on the batch
}


回答2:

Not using one of umpteen ORM libraries to do it ? Good :-)

To be completely safe when running scripts that do structural changes use SMO rather than SqlClient and make sure MARS is not turned on via connection string (SMO will normally complain if it is anyway). Look for ServerConnection class and ExecuteNonQuery - different DLL of course :-)

The diff is that SMO dll pases the script as-is to SQL so it's genuine equivalent of running it in SSMS or via isql cmd line. Slicing on GO-s ends up growing into much bigger scanning every time you encounter another glitch (like that GO can be in the middle of a multi-line comment, there can be multiple USE statements, a script can be dropping the very DB that SqlCLient connected to - oops :-). I just killed one such thing in the codebase I inherited (after more complex scripts conflicted with MARS and MARS is good for production code but not for admin stuff).