在C#中执行包含GO语句的SQL批处理

我正在尝试构建一个程序,它在批处理中执行sql语句并进行error handling(因此我没有使用SMO)。

问题是GO不是SQL的一部分,当使用.NET执行语句时,它最终会出错(SMO会处理它但不会给出执行失败的任何指示)。

string statements = File.ReadAllText("c:\\test.sql"); string[] splitted = statements.split("GO"); 

使用上面的行不能解决我的问题,因为GO关键字也可以进入注释(我不想从语句中删除注释)并且注释可以进入/ ** /或者在两个破折号之后 –
例如,我不希望解析以下代码:

 /* GO */ 

(ofc我谷歌搜索它,但那里没有解决方案)

ScriptDom

最简单的解决方案(也是最强大的)是使用T-SQL解析器。 好消息是你不必写它,只需添加引用:

  • Microsoft.Data.Schema.ScriptDom
  • Microsoft.Data.Schema.ScriptDom.Sql

然后使用代码:

 static void Main(string[] args) { string sql = @" /* GO */ SELECT * FROM [table] GO SELECT * FROM [table] SELECT * FROM [table] GO SELECT * FROM [table]"; string[] errors; var scriptFragment = Parse(sql, SqlVersion.Sql100, true, out errors); if (errors != null) { foreach (string error in errors) { Console.WriteLine(error); return; } } TSqlScript tsqlScriptFragment = scriptFragment as TSqlScript; if (tsqlScriptFragment == null) return; var options = new SqlScriptGeneratorOptions { SqlVersion = SqlVersion.Sql100, KeywordCasing = KeywordCasing.PascalCase }; foreach (TSqlBatch batch in tsqlScriptFragment.Batches) { Console.WriteLine("--"); string batchText = ToScript(batch, options); Console.WriteLine(batchText); } } public static TSqlParser GetParser(SqlVersion level, bool quotedIdentifiers) { switch (level) { case SqlVersion.Sql80: return new TSql80Parser(quotedIdentifiers); case SqlVersion.Sql90: return new TSql90Parser(quotedIdentifiers); case SqlVersion.Sql100: return new TSql100Parser(quotedIdentifiers); case SqlVersion.SqlAzure: return new TSqlAzureParser(quotedIdentifiers); default: throw new ArgumentOutOfRangeException("level"); } } public static IScriptFragment Parse(string sql, SqlVersion level, bool quotedIndentifiers, out string[] errors) { errors = null; if (string.IsNullOrWhiteSpace(sql)) return null; sql = sql.Trim(); IScriptFragment scriptFragment; IList errorlist; using (var sr = new StringReader(sql)) { scriptFragment = GetParser(level, quotedIndentifiers).Parse(sr, out errorlist); } if (errorlist != null && errorlist.Count > 0) { errors = errorlist.Select(e => string.Format("Column {0}, Identifier {1}, Line {2}, Offset {3}", e.Column, e.Identifier, e.Line, e.Offset) + Environment.NewLine + e.Message).ToArray(); return null; } return scriptFragment; } public static SqlScriptGenerator GetScripter(SqlScriptGeneratorOptions options) { if (options == null) return null; SqlScriptGenerator generator; switch (options.SqlVersion) { case SqlVersion.Sql80: generator = new Sql80ScriptGenerator(options); break; case SqlVersion.Sql90: generator = new Sql90ScriptGenerator(options); break; case SqlVersion.Sql100: generator = new Sql100ScriptGenerator(options); break; case SqlVersion.SqlAzure: generator = new SqlAzureScriptGenerator(options); break; default: throw new ArgumentOutOfRangeException(); } return generator; } public static string ToScript(IScriptFragment scriptFragment, SqlScriptGeneratorOptions options) { var scripter = GetScripter(options); if (scripter == null) return string.Empty; string script; scripter.GenerateScript(scriptFragment, out script); return script; } 

SQL Server管理对象

添加引用:

  • Microsoft.SqlServer.Smo
  • Microsoft.SqlServer.ConnectionInfo
  • Microsoft.SqlServer.Management.Sdk.Sfc

然后,您可以使用此代码:

 using (SqlConnection connection = new SqlConnection("Server=(local);Database=Sample;Trusted_Connection=True;")) { ServerConnection svrConnection = new ServerConnection(connection); Server server = new Server(svrConnection); server.ConnectionContext.ExecuteNonQuery(script); } 

CodeFluent运行时

CodeFluent运行时数据库有一个小的sql文件解析器。 它不处理复杂的情况,但例如支持注释。

 using (StatementReader statementReader = new CodeFluent.Runtime.Database.Management.StatementReader("GO", Environment.NewLine, inputStream)) { Statement statement; while ((statement = statementReader.Read(StatementReaderOptions.Default)) != null) { Console.WriteLine("-- "); Console.WriteLine(statement.Command); } } 

或者更简单

 new CodeFluent.Runtime.Database.Management.SqlServer.Database("connection string") .RunScript("path", StatementReaderOptions.Default); 

只有当“GO”站在一条孤独的线上或用空格时才会拆分,如下所示:

 Regex.Split(statements, @"^\s+GO\s+$"); 

是的,SSMS必须允许你解决问题。 正如你所提到的,它不是sql的一部分。 SSMS使用SMO来完成它的工作,这就是为什么它在那里工作。

正如您的评论所表明的那样,但问题很混乱,您需要在处理之前删除所有注释块。 如果您不想这样做,则需要将文件作为流处理并在/*处开始忽略并在*/ …处停止,并且可能还需要--\n|\r\n

您还可以使用正则表达式将其拆分(如果您将其作为文本blob读取,而不是已经被行拆分):

 var text = File.ReadAllText("file.txt") var cleanedText = Regex.Replace(text, @"/\*.*\*/", "", RegexOptions.Singleline) var parts = Regex.Split(cleanedText, @"^\s*GO.*$", RegexOptions.Multiline); for(var part in parts) { executeBatch(part); } // but this is getting ugly var str = "what /*\n the \n\n GO \n*/heck\nGO\nand then"; var cleanedText = Regex.Replace(str, @"/\*.*\*/", "\n", RegexOptions.Singleline) var split = Regex.Split(cleanedText, @"^\s*GO.*$", RegexOptions.Multiline); // == ["what\nheck", "\nand then"] 

是的,正如评论所说,你的真正答案是写一个解析器 。 即使你对评论的看法,你仍然可以在insert内的STRING中嵌入/**/ 。 所以…