GO 语句炸毁 .NET 中的 sql 执行
Posted
技术标签:
【中文标题】GO 语句炸毁 .NET 中的 sql 执行【英文标题】:GO statements blowing up sql execution in .NET 【发布时间】:2013-09-06 23:01:15 【问题描述】:我有一个非常简单的 C# 命令外壳应用程序,它执行由 SQL Server 生成的用于编写模式和数据的 sql 脚本。它在“GO”声明中爆炸。错误信息:
“GO”附近的语法不正确。
这是完整的 sql 脚本:
/****** 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
我的代码的相关部分如下所示:
SqlCommand command = new SqlCommand(script, connection);
command.CommandType = CommandType.Text;
command.ExecuteNonQuery();
有什么想法吗?
【问题讨论】:
重要:我的数据库有很多千兆字节,有很多表和存储过程。我正在为每个对象生成一个不同的文件,因为如果我在 1 个脚本中完成所有操作,我无法在任何文本编辑器中打开该文件,因为它太大了。进入并调整每个脚本以删除 GO 内容不是一种选择。我需要“按原样”使用脚本,尽管我可以完全控制脚本的生成方式——我可以在 TASKS->Generate Scripts 中选择这些选项中的任何一个。我正在执行多个脚本而不是 1 个脚本,因为 1 个脚本出错了,并且由于大小而我无法打开它。 How do I execute a large SQL script (with GO commands) from c#? 的可能重复项 【参考方案1】:正如其他人提到的,用GO
语句分割你的字符串。但请注意,您的脚本的其他部分可能包含文本 "GO"
。您可能在 GO 语句之前或之后也有空格,并且在 GO 语句之后的行上也可能有 cmets。其中任何一个在 SSMS 中都是有效的,因此您可能需要对其进行测试。
这是我使用的方法:
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'));
【讨论】:
您可以删除修剪并将RemoveAll替换为statements.RemoveAll(s => String.IsNullOrWhiteSpace(s))
,我刚刚对其进行了测试并将\r
和\n
视为检查中的空白。
好点。我实际上是在几年前写的,现在只是为了这个答案而把它拉出来。我会稍微调整一下并编辑。
这不考虑我们有来自编辑器的字符串的情况,例如“SELECT * FROM” + “SomeTable; GO” + “SELECT * FROM SomeOtherTable; GO”。任何想法,如果这有很多赞成票并且明显有效,我不想重写。我按“原样”使用它,这是怎么回事?
我无法让这个正则表达式匹配任何内容。有人可以发布一个可以使用的示例 sql 脚本吗?
像@MikeW一样,我无法让正则表达式匹配任何内容,所以我对其进行了一些修改:@"^[\t\r\n]*GO[\t\r\n]*\d*[\t\r\n]*(?:--.*)?$",
-- 匹配前后制表符、换行符,但不匹配空格,因为@987654328 @ 通常在自己的行上。我写了一堆单元测试,它在真实和人为的测试用例中运行良好。【参考方案2】:
如果您希望能够使用GO
,则需要参考以下dll
Microsoft.SqlServer.ConnectionInfo.dll Microsoft.SqlServer.Management.Sdk.Sfc.dll Microsoft.SqlServer.Smo.dll Microsoft.SqlServer.SqlEnum.dll
然后像这样执行
using (SqlConnection conn = new SqlConnection(connection))
Server db = new Server(new ServerConnection(conn));
string script = File.ReadAllText(scriptPath);
db.ConnectionContext.ExecuteNonQuery(script);
【讨论】:
效果很好!我在使用其他“解决方案”更改脚本时遇到了问题,因为脚本非常大且复杂,并且还有其他“关键”词。使用您的解决方案,无需拆分、删除或修改脚本。谢谢@iamkrillin .Net 4+ 存在问题 @Sandy 通过在 App.config 中添加标志轻松解决:http://***.com/questions/2455654/what-additional-configuration-is-necessary-to-reference-a-net-2-0-mixed-mode 这太棒了,帮我省了几个小时的拉头发时间! 我们需要引用另一个文件 ms.sql.batchparserclient dll 文件【参考方案3】:GO
不是 SQL 的一部分,它是 SQL Server Management Studio 为您拆分脚本所做的事情。
您需要做的是将查询读入一个字符串,然后在GO
上单独拆分一行(您可能需要为此使用正则表达式)
//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();
查看Matt Johnson's answer,了解GO
拆分的不那么天真的实现。
【讨论】:
【参考方案4】:GO 不是一个有效的 QA 命令,它是一个批处理分隔符...它由 Enterprise Manager 处理以分隔 SQL 脚本。因此,它将在企业管理器中工作,但不能在来自 C# 或其他外部程序的数据库调用中工作......
【讨论】:
【参考方案5】:正如另一个答案中提到的,GO
不受支持。
您可以使用GO
语句作为分隔符在脚本上使用String.Split()
,并将每个段作为命令单独运行。
【讨论】:
这样的简单拆分对于较大的脚本可能会很麻烦 - 假设某些文本将GO
作为文本的一部分,如列名或字符串的一部分 - 或者如果你只是寻找只有 GO
的行,该行可能在存储的 proc 创建脚本的文本内。
我同意,通常当我过去不得不这样做时,我实际上已经分裂,期待在 GO 语句之前和之后换行。但是正则表达式可能更合适。【参考方案6】:
作为按摩脚本以使其可通过 C# 运行的替代方法,您可以使用 sqlcmd
实用程序按原样运行它们。很多细节在:
http://technet.microsoft.com/en-us/library/ms180944.aspx
通过使用 sqlcmd,您可以编写任意数量的 SQL Server 生成脚本的执行脚本,而无需删除 Go
语句。
【讨论】:
【参考方案7】: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();
在这里找到。 http://blogs.msdn.com/b/onoj/archive/2008/02/26/incorrect-syntax-near-go-sqlcommand-executenonquery.aspx
【讨论】:
【参考方案8】:上面的答案有错误。 我刚刚测试了一个可行的解决方案: 你应该留出空间,';'或在 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();
【讨论】:
您要解析什么语句?在同一行中,分号在 GO 之前或之后均无效。例如。 “;去”和“去;”导致“解析 GO 时遇到不正确的语法”【参考方案9】:对“iamkrillin”的回答多加一点,使用旧的 DLL 使其工作。
添加对这些 DLL 的引用后
Microsoft.SqlServer.ConnectionInfo.dll , Microsoft.SqlServer.Management.Sdk.Sfc.dll Microsoft.SqlServer.Smo.dll , Microsoft.SqlServer.SqlEnum.dll
来自这样的地方:“C:\Program Files (x86)\Microsoft SQL Server\130\SDK\Assemblies\Microsoft.SqlServer.ConnectionInfo.dll” 到项目中,我需要在我的代码文件顶部添加以下“使用”指令:
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);
【讨论】:
【参考方案10】:FluentMigrator 库中有一个非常好的 SqlServerBatchParser 类
支持GO
GO 3
语句。
你需要安装FluentMigrator.Runner.SqlServer nuget包
从 FluentMigrator 源代码中大量复制(稍作修改)的示例:
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();
致谢:https://github.com/fluentmigrator/fluentmigrator/blob/v3.2.1/src/FluentMigrator.Runner.SqlServer/Processors/SqlServer/SqlServerProcessor.cs
【讨论】:
【参考方案11】:只需将“GO”替换为“”即可。
SqlCommand command = new SqlCommand(script.Replace("GO", ""), connection);
command.CommandType = CommandType.Text;
command.ExecuteNonQuery();
【讨论】:
以上是关于GO 语句炸毁 .NET 中的 sql 执行的主要内容,如果未能解决你的问题,请参考以下文章