43

I have a very simple C# command shell app that executes a sql script generated by SQL Server for scripting schema and data. It's blowing up on the "GO" statements. Error message:

Incorrect syntax near 'GO'.

Here is the full sql script:

/****** Object:  Table [gym].[MembershipStatus]    Script Date: 9/3/2013 9:24:01 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [gym].[MembershipStatus](
    [MembershipStatusID] [tinyint] IDENTITY(1,1) NOT NULL,
    [Name] [varchar](75) NOT NULL,
    [Description] [varchar](400) NOT NULL,
    [AllowCheckin] [bit] NOT NULL,
    [IncludeInCollections] [bit] NOT NULL,
    [ScheduleFutureInvoices] [bit] NOT NULL,
 CONSTRAINT [MembershipStatus_PK] PRIMARY KEY CLUSTERED 
(
    [MembershipStatusID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF
GO
SET IDENTITY_INSERT [gym].[MembershipStatus] ON 

INSERT [gym].[MembershipStatus] ([MembershipStatusID], [Name], [Description], [AllowCheckin], [IncludeInCollections], [ScheduleFutureInvoices]) VALUES (1, N'Active', N'Active', 1, 1, 1)
INSERT [gym].[MembershipStatus] ([MembershipStatusID], [Name], [Description], [AllowCheckin], [IncludeInCollections], [ScheduleFutureInvoices]) VALUES (2, N'Cancelled', N'Cancelled', 0, 1, 0)
INSERT [gym].[MembershipStatus] ([MembershipStatusID], [Name], [Description], [AllowCheckin], [IncludeInCollections], [ScheduleFutureInvoices]) VALUES (3, N'Collection', N'Collection', 0, 0, 0)
INSERT [gym].[MembershipStatus] ([MembershipStatusID], [Name], [Description], [AllowCheckin], [IncludeInCollections], [ScheduleFutureInvoices]) VALUES (4, N'Deleted', N'Deleted', 0, 0, 0)
INSERT [gym].[MembershipStatus] ([MembershipStatusID], [Name], [Description], [AllowCheckin], [IncludeInCollections], [ScheduleFutureInvoices]) VALUES (5, N'Expired', N'Expired', 1, 1, 1)
INSERT [gym].[MembershipStatus] ([MembershipStatusID], [Name], [Description], [AllowCheckin], [IncludeInCollections], [ScheduleFutureInvoices]) VALUES (6, N'Freeze', N'Freeze', 0, 1, 0)
INSERT [gym].[MembershipStatus] ([MembershipStatusID], [Name], [Description], [AllowCheckin], [IncludeInCollections], [ScheduleFutureInvoices]) VALUES (7, N'Inactive', N'Inactive', 0, 1, 1)
SET IDENTITY_INSERT [gym].[MembershipStatus] OFF
ALTER TABLE [gym].[MembershipStatus] ADD  DEFAULT ('') FOR [Name]
GO
ALTER TABLE [gym].[MembershipStatus] ADD  DEFAULT ('') FOR [Description]
GO
ALTER TABLE [gym].[MembershipStatus] ADD  DEFAULT ((0)) FOR [AllowCheckin]
GO
ALTER TABLE [gym].[MembershipStatus] ADD  DEFAULT ((0)) FOR [IncludeInCollections]
GO
ALTER TABLE [gym].[MembershipStatus] ADD  DEFAULT ((0)) FOR [ScheduleFutureInvoices]
GO

The relevant section of my code looks like this:

SqlCommand command = new SqlCommand(script, connection);
command.CommandType = CommandType.Text;
command.ExecuteNonQuery();

Any ideas?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
HerrimanCoder
  • 6,835
  • 24
  • 78
  • 158
  • Important: My database is many gigabytes with many tables and stored procs. I'm generating a different file for each object because if I do it all in 1 script, I cannot open the file in any text editor because it's too big. Going in and tweaking each script to remove the GO stuff is not an option. I need to get this working with the scripts "as-is", though I do have complete control over how the scripts get generated -- I can choose any of those options in TASKS->Generate Scripts. I'm doing multiple scripts instead of 1 because the 1 script is erroring out, and I can't open it because of size. – HerrimanCoder Sep 03 '13 at 16:13
  • possible duplicate of [How do I execute a large SQL script (with GO commands) from c#?](http://stackoverflow.com/questions/40814/how-do-i-execute-a-large-sql-script-with-go-commands-from-c) – iamkrillin Sep 03 '13 at 16:24

14 Answers14

39

As others mentioned, split your string by GO statements. But be careful, you may have the text "GO" in other parts of your script. You might also have whitespace before or after the GO statement, and you might have comments on the line after the GO statement also. Any of that would be valid in SSMS, so you may want to test for it.

Here is the method I use:

private static IEnumerable<string> SplitSqlStatements(string sqlScript)
{
    // Make line endings standard to match RegexOptions.Multiline
    sqlScript = Regex.Replace(sqlScript, @"(\r\n|\n\r|\n|\r)", "\n");

    // Split by "GO" statements
    var statements = Regex.Split(
            sqlScript,
            @"^[\t ]*GO[\t ]*\d*[\t ]*(?:--.*)?$",
            RegexOptions.Multiline |
            RegexOptions.IgnorePatternWhitespace |
            RegexOptions.IgnoreCase);

    // Remove empties, trim, and return
    return statements
        .Where(x => !string.IsNullOrWhiteSpace(x))
        .Select(x => x.Trim(' ', '\n'));
}
muusbolla
  • 637
  • 7
  • 20
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • You could remove the trims and replace the RemoveAll with `statements.RemoveAll(s => String.IsNullOrWhiteSpace(s))`, I just tested it and treats `\r` and `\n` as white space in the check. – Scott Chamberlain Sep 03 '13 at 16:34
  • Good point. I actually wrote this years ago and just pulled it now for this answer. I'll tune it up a bit and edit. – Matt Johnson-Pint Sep 03 '13 at 16:36
  • @ScottChamberlain - Updated. I still want to trim though, because there could be blank lines or whitespace at the begining of a valid string. Like `"\r\n SELECT"`... – Matt Johnson-Pint Sep 03 '13 at 16:43
  • That would only matter if the code was going to be read by a human, `SqlCommand` does not care about white space leading or trailing (or even in the middle of the query), sense the entire point of this function is to make the script *machine readable* I don't think they are needed and only serve to make the program slower by allocating more strings. – Scott Chamberlain Sep 03 '13 at 16:45
  • I've created unit tests for this function at https://github.com/MNF/CommonDotNetHelpers/blob/master/tests/EmbeddedSqlScriptsHelperTests.cs – Michael Freidgeim Jul 01 '16 at 12:44
  • 2
    This does not account for cases where we have strings coming from an editor with strings like "SELECT * FROM " + "SomeTable; GO " + "SELECT * FROM SomeOtherTable; GO". Any thoughts, I don't want to re-write if this has some many upvotes and clearly works. I am using it "as is", whats going on here? – MoonKnight Aug 09 '16 at 09:22
  • @Killercam - That wouldn't work anyway. From [the MSDN docs](https://msdn.microsoft.com/en-us/library/ms188037.aspx): `"A Transact-SQL statement cannot occupy the same line as a GO command"`. – Matt Johnson-Pint Aug 09 '16 at 15:37
  • Slight adjustment to regex to account for `GO [count]` and clean up spacing. – Matt Johnson-Pint Aug 09 '16 at 15:39
  • I've changed the regex as following, it was matching "GO\r\n--COMMENT". Regex: @"^[ \t]*GO[ \t]*\d*[ \t]*(\-\-[^\r]*?)*$" – Dan Oct 06 '16 at 14:48
  • Although it is not foolproof this works for me script = script.Replace("GO\r\n", "\r\n"); – andrew pate Mar 16 '17 at 11:17
  • @andrewpate - The regex approach covers many more edge cases. Also, I updated the regex one more time just to make it cleaner. It still matches on the items previously discussed. – Matt Johnson-Pint Mar 16 '17 at 16:24
  • 1
    I can't get this Regex to match anything. Can someone posted an example sql script that this works on? – Mike W Jun 27 '17 at 13:35
  • I had a table named CATEGORY and this regex splits it into two lines. Here is a statement IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[CATEGORY]') AND type in (N'U')). It catches the GO in CATEGORY. Please correct this. – devanalyst Aug 19 '17 at 08:09
  • 9
    Like @MikeW, I couldn't get the regex to match anything, so I modified it a bit: `@"^[\t\r\n]*GO[\t\r\n]*\d*[\t\r\n]*(?:--.*)?$",` -- match preceding and trailing tabs, newlines, but not spaces, since `GO` is typically on its own line. I wrote a bunch of unit tests, and it works OK in real and contrived test cases. – rianjs Sep 05 '17 at 17:23
  • @rianjs Regex should be the accepted answer, the one that Matt wrote doesn't match anything. – HamsterWithPitchfork May 31 '18 at 09:25
  • @metabuddy - thanks for reminding me. I've edited Matt's answer to use my regex to hopefully save people some time. – rianjs May 31 '18 at 13:13
  • @metabuddy - Thanks - but adding `\r` and `\n` everywhere doesn't make sense here, nor does removing the space character. The things that need to be matched are always single-line statements starting with `GO`, optionally followed by a number, optionally followed by a comment. The components of the statement *cannot* be split across multiple lines. I will see if I can improve the regex from what it was, but this last edit doesn't make sense. – Matt Johnson-Pint May 31 '18 at 17:29
  • @metabuddy - I rolled back to my prior regex, as it matches just fine. I'm not sure why you and others say it dosn't. Please take a look at [the proof here](https://regex101.com/r/hPDmvR/1). – Matt Johnson-Pint May 31 '18 at 17:36
  • @MattJohnson I didn't investigate in details, but I took your Regex, placed it into a simple method, so I could execute TSQL code that's generated by SSMS. With the original regex it failed to execute, then I updated Regex to what Rianjs had posted and it executed successfully. – HamsterWithPitchfork Jun 04 '18 at 11:43
  • @metabuddy Did you take just the regex, or the entire method? Especially the multiline option is critical on the regex. – Matt Johnson-Pint Jun 04 '18 at 16:46
  • I think I've found why some people have had problems with this function but Matt has found that it works fine - it's to do with line endings. The regex end of line match ($) will match \n but not \r. If the input data has old mac line endings (just \r) or even standard windows line endings (\r\n) then the regex does not match. I have fixed it by replacing all non-matching line endings with a simple \n. This would work with any combination. If you want to make it faster and just deal with the windows \r\n case then add \r? before the $. – Peter Davidson Aug 14 '18 at 11:57
  • See https://stackoverflow.com/questions/16549668/treating-r-as-n-in-c-sharp-regex for more details (this was where the line ending regex came from). – Peter Davidson Aug 14 '18 at 11:59
  • Nice Solution, it worked for me (DotNet Core 2.1) but i needed also [Microsoft.SqlServer.SqlManagementObjects](https://www.nuget.org/packages/Microsoft.SqlServer.SqlManagementObjects) find it on answer [stackoverflow.com/questions/45549731](https://stackoverflow.com/questions/45549731/could-not-load-type-microsoft-sqlserver-server-sqlcontext-from-assembly-syste) – George Stavrou May 09 '19 at 12:03
  • To make this work on my windows machine I added the following line at the beginning of the function (replace all line ending types with standard \n, which RegexOptions.Multiline will match): sqlScript = Regex.Replace(sqlScript, @"(\r\n|\n\r|\n|\r)", "\n"); – muusbolla Sep 15 '21 at 17:36
34

If you want to be able to use GO you will need to reference to the following dlls

Microsoft.SqlServer.ConnectionInfo.dll
Microsoft.SqlServer.Management.Sdk.Sfc.dll
Microsoft.SqlServer.Smo.dll
Microsoft.SqlServer.SqlEnum.dll

Then execute like so

 using (SqlConnection conn = new SqlConnection(connection))
 {
     Server db = new Server(new ServerConnection(conn));
     string script = File.ReadAllText(scriptPath);
     db.ConnectionContext.ExecuteNonQuery(script);      
 }
Jesus is Lord
  • 14,971
  • 11
  • 66
  • 97
iamkrillin
  • 6,798
  • 1
  • 24
  • 51
  • This worked beautifully! I had a problem altering the scripts using the other "solutions" since the scripts where quite large and complicated and there were other "key" words. With your solution, no need to split, remove, or modify the scripts. Thanks @iamkrillin – MaC Jan 15 '16 at 18:28
  • 1
    Problematic with .Net 4+ – Sandy Jul 06 '16 at 18:16
  • 1
    @Sandy Easily resolved by adding a flag in App.config: [http://stackoverflow.com/questions/2455654/what-additional-configuration-is-necessary-to-reference-a-net-2-0-mixed-mode](http://stackoverflow.com/questions/2455654/what-additional-configuration-is-necessary-to-reference-a-net-2-0-mixed-mode) – pymaxion Aug 12 '16 at 15:49
  • This is fantastic and just saved me hours of hair pulling! – Stephen Byrne Jun 04 '17 at 14:59
  • we need to reference another file ms.sql.batchparserclient dll file – KoolKabin Nov 16 '17 at 00:20
  • There is currently a pre-release nuget package of Microsoft.SqlServer.Smo which will add related dependencies. I'm using them after adding a registry entry to disable strong name validation for Microsoft.SqlServer.ConnectionInfo.dll which is the one that will fail. – dudeNumber4 Sep 27 '18 at 14:55
  • But this potentially locks you to a version of database and it's installation. Though there is an unofficial nuget package to support these assemblies, it's not guaranteed for support in future. So use this with caution – Soundararajan Jun 28 '20 at 06:27
  • This is the only right answer. This allows scripts to be run as written with "GO" keywords and "BEGIN TRANSACTIONS/COMMIT" that span go statements. The app.config is not required. This should be marked as the correct answer. – BWayne Jun 09 '22 at 15:42
  • Installing these packages didn't work for me in a .NET 6 project. Screenshot of references with warnings after installation https://ibb.co/TBT25Xr – Jesus is Lord Aug 06 '22 at 15:36
  • I can confirm that with current .NET 6 this works with the official Nuget package https://www.nuget.org/packages/Microsoft.SqlServer.SqlManagementObjects – JSancho Nov 14 '22 at 11:02
13

GO is not part of SQL, it is something SQL Server Management Studio does for you to split the script up.

What you need to do is read the query in to a string then split on GO on a line by itself (you may want to use Regex for this)

//Its better to dispose the SqlCommand, I also switched constructors so I could re-use the SqlCommand.
using(SqlCommand command = new SqlCommand())
{
    command.Connection = connection;

    var scripts = Regex.Split(script, @"^\w+GO$", RegexOptions.Multiline);
    foreach(var splitScript in scripts)
    {
        command.CommandText = splitScript;
        command.ExecuteNonQuery();
    }
}

Look at Matt Johnson's answer for a less naive implementation of the GO splitting.

Community
  • 1
  • 1
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
9

GO is not a valid QA command, it is a batch separator... It is processed by Enterprise Manager to separate SQL scripts. As such, it will work in Enterprise Manager, but not in database calls from C# or other external programs....

Sparky
  • 14,967
  • 2
  • 31
  • 45
3

This answer was very helpful, but ultimately did not work for me, as-is. I have a .NET Framework 6.0 project. Those dll's didn't work.

To get this code working:

using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;

Server db = new Server(new ServerConnection(conn));
db.ConnectionContext.ExecuteNonQuery(sql);

I needed to install Microsoft.SqlServer.SqlManagementObjects using NuGet (161.47021.0)

Then, SQL with GO executed without error

Jesus is Lord
  • 14,971
  • 11
  • 66
  • 97
2

As mentioned in another answer, GO is not supported.

You can use String.Split() on your script using your GO statements as delimiters, and run each segment as a command, separately.

Pablo Romeo
  • 11,298
  • 2
  • 30
  • 58
  • A simple split like this may be troublesome for larger scripts - suppose some of the text has `GO` as part of the text, like part of a column name or string - or if you're only looking for lines with only `GO` on them, that line may be is inside the text of a stored proc creation script. – Joe Enos Sep 03 '13 at 16:27
  • I agree, usually when I've had to do this in the past I've actually split expecting a newline before and after the GO statement. But a Regex may be more appropriate. – Pablo Romeo Sep 03 '13 at 16:32
2

As an alternative to massaging the scripts to make them runnable through C#, you could just run them as-is by using the sqlcmd utility. Lot of details at:

http://technet.microsoft.com/en-us/library/ms180944.aspx

By using sqlcmd, you can script out the execution of any number of your SQL Server generated scripts, without stripping out the Go statements.

Eric King
  • 11,594
  • 5
  • 43
  • 53
1
string[] commands = sql.Split( 
    new string[]{"GO\r\n", "GO ", "GO\t"}, StringSplitOptions.RemoveEmptyEntries );
foreach (string c in commands)
{
    command = new SqlCommand(c, masterConnection);
    command.ExecuteNonQuery();
}
}
catch (Exception e)
{
    MessageBox.Show(e.Message);
}
finally
{
    masterConnection.Close();
}
}

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

Tasos K.
  • 7,979
  • 7
  • 39
  • 63
0

The top answer has a mistake. I just tested a working solution: You should allow space,';' or new line before GO

            var scripts = Regex.Split(statementText, @"(\s+|;|\n|\r)GO", RegexOptions.Multiline);
            foreach(var splitScript in scripts.Where(splitScript => !splitScript.IsNullOrWhiteSpace())) {
                cmd.CommandText = splitScript;
                cmd.ExecuteNonQuery();
            }
Sergey T
  • 161
  • 1
  • 6
  • 1
    What statement you are trying to parse? Semicolon is not valid before or after GO in the same line. E.g. both ";go" and "go;" cause "Incorrect syntax was encountered while parsing GO" – Michael Freidgeim Jun 10 '16 at 01:32
0

1 additional point to "iamkrillin"'s answer, for using the old DLLs to make it work.

after adding the references to these DLLs

Microsoft.SqlServer.ConnectionInfo.dll , Microsoft.SqlServer.Management.Sdk.Sfc.dll Microsoft.SqlServer.Smo.dll , Microsoft.SqlServer.SqlEnum.dll

from a place like this: "C:\Program Files (x86)\Microsoft SQL Server\130\SDK\Assemblies\Microsoft.SqlServer.ConnectionInfo.dll" to the project, I needed to add the following "using" directives to the top of my code file:

using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;

....
string query = @" //sql multi-command text here"

using (SqlConnection thisconn = new SqlConnection(connectionString)) {
    Server db = new Server(new ServerConnection(thisconn));
    db.ConnectionContext.ExecuteNonQuery(query);
}
0

There is very nice SqlServerBatchParser class inside FluentMigrator library

Supports GO GO 3 statements.

You need to install FluentMigrator.Runner.SqlServer nuget package

Example generously copied(slightly modified) from FluentMigrator source code:

using FluentMigrator.Runner.BatchParser;
using FluentMigrator.Runner.BatchParser.SpecialTokenSearchers;
using FluentMigrator.Runner.BatchParser.Sources;

void Main()
{
    var connString = "Server=.;Database=mydb;Trusted_Connection=True;";
    var sql = @"select 1;
GO
SELECT 2;
GO 5";
    ExecuteBatchNonQuery(connString, sql);
}

public static void ExecuteBatchNonQuery(string ConnectionString, string sql)
{
    var sqlBatch = string.Empty;
    var conn = new SqlConnection(ConnectionString);
    conn.Open();

    try
    {
        var parser = new SqlServerBatchParser();
        parser.SqlText += (sender, args) => { sqlBatch = args.SqlText.Trim(); };
        parser.SpecialToken += (sender, args) =>
        {
            if (string.IsNullOrEmpty(sqlBatch))
                return;

            if (args.Opaque is GoSearcher.GoSearcherParameters goParams)
            {
                using (var command = conn.CreateCommand())
                {
                    command.CommandText = sqlBatch;

                    for (var i = 0; i != goParams.Count; ++i)
                    {
                        command.ExecuteNonQuery();
                    }
                }
            }

            sqlBatch = null;
        };

        using (var source = new TextReaderSource(new StringReader(sql), true))
        {
            parser.Process(source, stripComments: true);
        }

        if (!string.IsNullOrEmpty(sqlBatch))
        {
            using (var command = conn.CreateCommand())
            {
                command.CommandText = sqlBatch;
                command.ExecuteNonQuery();
            }
        }
    }
    catch (Exception ex)
    {
        using (var message = new StringWriter())
        {
            message.WriteLine("An error occured executing the following sql:");
            message.WriteLine(string.IsNullOrEmpty(sqlBatch) ? sql : sqlBatch);
            message.WriteLine("The error was {0}", ex.Message);

            throw new Exception(message.ToString(), ex);
        }
    }
    finally
    {
        conn?.Dispose();
    }
}

Credits: https://github.com/fluentmigrator/fluentmigrator/blob/v3.2.1/src/FluentMigrator.Runner.SqlServer/Processors/SqlServer/SqlServerProcessor.cs

Didar_Uranov
  • 1,230
  • 11
  • 26
0

Just replace "GO" with "" and it works.

SqlCommand command = new SqlCommand(script.Replace("GO", ""), connection);
command.CommandType = CommandType.Text;
command.ExecuteNonQuery();
Matt Ke
  • 3,599
  • 12
  • 30
  • 49
0

I had this issue back when I started coding in C# I have used and tested all possible answers but non led to the answer I loved. Finally I found a good way to do it without using any regex or third-party frameworks. many people here are having plausible answers but none worked for me, here is how I did it:


   string FileUploaded = File.ReadAllText(filePath);
   string[] fileSplited = FileUploaded.Split(new string[] { "GO" }, StringSplitOptions.None);

   using (SqlConnection conn = new SqlConnection(connStr))
   using (SqlCommand cmd = new SqlCommand())
   {
                        
       cmd.Connection = conn;
       conn.Open();

       foreach (string str in fileSplited)
       {
            cmd.CommandText = str;
            cmd.ExecuteNonQuery();
       }                 
   }

yo can also use try-catch block to help you debug better. AS you can see this is so similar to all the answers above but the only difference is the StringSplitOptions.None option that I see no one mentioned

Hope it helps

Atrin Noori
  • 311
  • 3
  • 12
0

I know this question is a little old but I thought I'd add another way to do this via the sqlcmd utility using System.Diagnostics.Process.

public void ExecuteSQLFromFile(string filePath, string sqlInstance)
{
    if(File.Exists(filePath))
    {
        var p = new Process();
        p.StartInfo.Verb = "runas";
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.FileName = "sqlcmd";
        p.StartInfo.Arguments = "-S " + sqlInstance + " -i " + filePath;
        p.Start();

        var standardOutput = p.StandardOutput.ReadToEnd();
        var standardError = p.StandardError.ReadToEnd();
        p.WaitForExit();
    }
}

Link to learn.ms.com article with sqlcmd downloads and usage.