Мне нужно выполнить большой набор операторов SQL (создание группы таблиц, представлений и хранимых процедур) из программы на C#.
Эти операторы должны быть разделены операторами GO, но SqlCommand.ExecuteNonQuery() не любит операторы GO. Мое решение, которое я опубликую для справки, заключалось в том, чтобы разделить строку SQL на строки GO и выполнить каждый пакет отдельно.
Есть ли способ проще / лучше?





Я также столкнулся с той же проблемой, и я не мог найти другого способа, кроме разделения одной операции SQL на отдельные файлы, а затем выполнения их всех по очереди.
Очевидно, проблема не в списках команд DML, они могут выполняться без перехода между ними; другая история с DDL (создать, изменить, отбросить ...)
Это то, что я собрал вместе, чтобы решить мою непосредственную проблему.
private void ExecuteBatchNonQuery(string sql, SqlConnection conn) {
string sqlBatch = string.Empty;
SqlCommand cmd = new SqlCommand(string.Empty, conn);
conn.Open();
sql += "\nGO"; // make sure last batch is executed.
try {
foreach (string line in sql.Split(new string[2] { "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries)) {
if (line.ToUpperInvariant().Trim() == "GO") {
cmd.CommandText = sqlBatch;
cmd.ExecuteNonQuery();
sqlBatch = string.Empty;
} else {
sqlBatch += line + "\n";
}
}
} finally {
conn.Close();
}
}
Он требует, чтобы команды GO находились в отдельной строке и не обнаруживают блочные комментарии, поэтому такие вещи будут разделены и вызовут ошибку:
ExecuteBatchNonQuery(@"
/*
GO
*/", conn);
Я хочу запустить этот код с помощью сценария SQL с несколькими хранимыми процедурами внутри, но я немного запутался, где он читает SQL? Когда вы говорите о «последней партии», вы имеете в виду код SQL? И если да, то как бы вы определяли последний пакет, и что, если бы я хотел запустить все пакеты, а не только последний? Я знаю слишком много вопросов, но спасибо, если у вас есть время ответить.
Вы передаете SQL в функцию в виде строки: string sql - это весь сценарий. Когда я говорю о «пакете», я имею в виду фрагмент кода SQL между двумя операторами «GO». Код добавляет GO в конец скрипта, чтобы код внутри foreach не пропустил последний пакет, если вы не завершили свой скрипт GO. Таким образом, написанный код будет выполнять весь SQL.
Я создал метод расширения: внутренний статический класс SqlCommandHelper {внутренний статический void ExecuteBatchNonQuery (это SqlCommand cmd, строка sql)
Если вы хотите быть немного более эффективным, вы можете использовать StringBuilder sqlBatch.
Другая аналогичная реализация с использованием регулярного выражения см. stackoverflow.com/questions/18596876/…
Используйте объекты управления SQL Server (SMO), которые понимают разделители GO. См. Мое сообщение в блоге здесь: http://weblogs.asp.net/jongalloway/Handling-_2200_GO_2200_-Separators-in-SQL-Scripts-2D00-the-easy-way
Образец кода:
public static void Main()
{
string scriptDirectory = "c:\temp\sqltest\";
string sqlConnectionString = "Integrated Security=SSPI;" +
"Persist Security Info=True;Initial Catalog=Northwind;Data Source=(local)";
DirectoryInfo di = new DirectoryInfo(scriptDirectory);
FileInfo[] rgFiles = di.GetFiles("*.sql");
foreach (FileInfo fi in rgFiles)
{
FileInfo fileInfo = new FileInfo(fi.FullName);
string script = fileInfo.OpenText().ReadToEnd();
using (SqlConnection connection = new SqlConnection(sqlConnectionString))
{
Server server = new Server(new ServerConnection(connection));
server.ConnectionContext.ExecuteNonQuery(script);
}
}
}
Если это не сработает для вас, посмотрите библиотеку Фила Хаака, которая справляется с этим: http://haacked.com/archive/2007/11/04/a-library-for-executing-sql-scripts-with-go-separators-and.aspx
Как это можно интегрировать с транзакцией? Код выдает InvalidOperationException при создании ServerConnection с SqlConnection, на котором есть ожидающая транзакция.
Это решение работает, я просто хочу добавить, что если вы хотите использовать сделки с объектом TransactionScope, вам просто нужно подключить соединение с текущей внешней транзакцией. Проверьте мой ответ здесь: stackoverflow.com/a/18322938/1268570
отлично работает, но можем ли мы использовать SqlConnection.InfoMessage), чтобы увидеть результат в приложении C# или же сохранить результат в файле txt, просто чтобы узнать, был ли сценарий успешно выполнен, потому что недавно использовал sqlcmd, когда я выполнил файл сценария 150 МБ на удаленном хосте, после 55 минут производилось несколько строк с этой ошибкой, TCP Provider: An existing connection was forcibly closed by the remote host., communication link failure. , невозможно узнать ни одну из обработанных строк, но меня беспокоят сообщения об ошибках при запуске файла сценария, созданного базой данных.
Это решение привело к сбою вашего кода, если на компьютере не установлены некоторые библиотеки DLL SQL. .NET использует некоторые библиотеки DLL, встроенные в Windows. Отсутствие некоторых пакетов функций SQL (в том числе объектов управления) может предотвратить появление таких ошибок, как «Microsoft.SqlServer.SqlClrProvider.dll не найден». Исправив это (это непростая работа), следующей ошибкой будет Microsoft.SqlServer.BathParser.dll и т. д. Найдите другое решение для обеспечения гибкости вашего приложения.
Это не работает, особенно если там есть оператор изменения процедуры. Он просто жалуется, что это должен быть первый оператор в пакете, потому что прямо перед ним стоит разделитель пакетов, но это все равно вызывает ошибку.
ExecuteNonQuery бесполезен, если вы пытаетесь вернуть результаты. И когда вы используете ExecuteWithResults, он не может обрабатывать разделители пакетов. Невероятный. У него также нет способа захватить затронутые строки для отдельных операторов, как это делает SSMS.
Для этого можно использовать Объекты управления SQL. Это те же объекты, которые Management Studio использует для выполнения запросов. Я считаю, что Server.ConnectionContext.ExecuteNonQuery() выполнит то, что вам нужно.
Ключевое слово разделителя пакетов «GO» фактически используется самой SQL Management Studio, поэтому оно знает, где завершить пакеты, отправляемые на сервер, и не передается серверу SQL. Вы даже можете изменить ключевое слово в Management Studio, если захотите.
Если вы не хотите идти по маршруту SMO, вы можете найти и заменить «GO» на «;» и запрос, как и вы. Обратите внимание, что будет возвращен последний набор результатов.
Они выполняют ExecuteNonQuery. Это намного более простой способ.
Использование «GO» позволит вам снова определить те же переменные в следующей команде в пакете. Если поставить точку с запятой, этого не произойдет.
Если вы не хотите устанавливать объекты SMO, вы можете использовать инструмент gplex (см. этот ответ)
Слишком трудно :)
Создайте массив строк str [], заменив GO на ", @":
string[] str = {
@"
USE master;
",@"
CREATE DATABASE " +con_str_initdir+ @";
",@"
-- Verify the database files and sizes
--SELECT name, size, size*1.0/128 AS [Size in MBs]
--SELECT name
--FROM sys.master_files
--WHERE name = N'" + con_str_initdir + @"';
--GO
USE " + con_str_initdir + @";
",@"
SET ANSI_NULLS ON
",@"
SET QUOTED_IDENTIFIER ON
",@"
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Customers]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[Customers](
[CustomerID] [int] IDENTITY(1,1) NOT NULL,
[CustomerName] [nvarchar](50) NULL,
CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED
(
[CustomerID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
",@"
SET ANSI_NULLS ON
",@"
SET QUOTED_IDENTIFIER ON
",@"
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GOODS]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[GOODS](
[GoodsID] [int] IDENTITY(1,1) NOT NULL,
[GoodsName] [nvarchar](50) NOT NULL,
[GoodsPrice] [float] NOT NULL,
CONSTRAINT [PK_GOODS] PRIMARY KEY CLUSTERED
(
[GoodsID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
",@"
SET ANSI_NULLS ON
",@"
SET QUOTED_IDENTIFIER ON
",@"
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Orders]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[Orders](
[OrderID] [int] IDENTITY(1,1) NOT NULL,
[CustomerID] [int] NOT NULL,
[Date] [smalldatetime] NOT NULL,
CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED
(
[OrderID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
",@"
SET ANSI_NULLS ON
",@"
SET QUOTED_IDENTIFIER ON
",@"
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[OrderDetails]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[OrderDetails](
[OrderID] [int] NOT NULL,
[GoodsID] [int] NOT NULL,
[Qty] [int] NOT NULL,
[Price] [float] NOT NULL,
CONSTRAINT [PK_OrderDetails] PRIMARY KEY CLUSTERED
(
[OrderID] ASC,
[GoodsID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
",@"
SET ANSI_NULLS ON
",@"
SET QUOTED_IDENTIFIER ON
",@"
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[InsertCustomers]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql @statement = N'-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
create PROCEDURE [dbo].[InsertCustomers]
@CustomerName nvarchar(50),
@Identity int OUT
AS
INSERT INTO Customers (CustomerName) VALUES(@CustomerName)
SET @Identity = SCOPE_IDENTITY()
'
END
",@"
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_Orders_Customers]') AND parent_object_id = OBJECT_ID(N'[dbo].[Orders]'))
ALTER TABLE [dbo].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([CustomerID])
REFERENCES [dbo].[Customers] ([CustomerID])
ON UPDATE CASCADE
",@"
ALTER TABLE [dbo].[Orders] CHECK CONSTRAINT [FK_Orders_Customers]
",@"
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_OrderDetails_GOODS]') AND parent_object_id = OBJECT_ID(N'[dbo].[OrderDetails]'))
ALTER TABLE [dbo].[OrderDetails] WITH CHECK ADD CONSTRAINT [FK_OrderDetails_GOODS] FOREIGN KEY([GoodsID])
REFERENCES [dbo].[GOODS] ([GoodsID])
ON UPDATE CASCADE
",@"
ALTER TABLE [dbo].[OrderDetails] CHECK CONSTRAINT [FK_OrderDetails_GOODS]
",@"
IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_OrderDetails_Orders]') AND parent_object_id = OBJECT_ID(N'[dbo].[OrderDetails]'))
ALTER TABLE [dbo].[OrderDetails] WITH CHECK ADD CONSTRAINT [FK_OrderDetails_Orders] FOREIGN KEY([OrderID])
REFERENCES [dbo].[Orders] ([OrderID])
ON UPDATE CASCADE
ON DELETE CASCADE
",@"
ALTER TABLE [dbo].[OrderDetails] CHECK CONSTRAINT [FK_OrderDetails_Orders]
"};
for(int i =0; i<str.Length;i++)
{
myCommand.CommandText=str[i];
try
{
myCommand.ExecuteNonQuery();
}
catch (SystemException ee)
{
MessageBox.Show("Error "+ee.ToString());
}
}
Вот и все, наслаждайтесь.
Я сделал это сегодня, загрузив свой SQL из текстового файла в одну строку. Затем я использовал функцию разделения строки, чтобы разделить строку на отдельные команды, которые затем отправлялись на сервер по отдельности. Простые :)
Только что понял, что вам нужно разделить \ nGO на тот случай, если буквы GO появятся в любом из ваших имен таблиц и т. д. Думаю, мне там повезло!
На основе решения Blorgbeard.
foreach (var sqlBatch in commandText.Split(new[] { "GO" }, StringSplitOptions.RemoveEmptyEntries))
{
sqlCommand.CommandText = sqlBatch;
sqlCommand.ExecuteNonQuery();
}
new [] {"GO", "Go", "go"}
new [] {"GO", "Go", "go", "gO"}
Работает до тех пор, пока у вас нет другого использования для двух букв в вашем коде, например GOTO-Statements или комментариев.
У меня была такая же проблема в java, и я решил ее с помощью логики и регулярного выражения. Я считаю, что можно применить ту же логику. Сначала я читаю из файла slq в память. Затем применяю следующую логику. Это в значительной степени то, что было сказано ранее, однако я считаю, что использование регулярного выражения с привязкой к слову безопаснее, чем ожидание новой строки char.
String pattern = "\bGO\b|\bgo\b";
String[] splitedSql = sql.split(pattern);
for (String chunk : splitedSql) {
getJdbcTemplate().update(chunk);
}
Это в основном разбивает строку sql на массив строк sql. Регулярное выражение в основном предназначено для обнаружения полных «идущих» слов в нижнем или верхнем регистре. Затем вы последовательно выполняете разные запросы.
Осторожно: как вы это разделите? insert into books values ('1478355824', 'An Introduction To Programming in Go (paperback)', 9.00)
Хороший момент :-) В моей ситуации не было вставок данных. Просто создавал таблицы, хранимые процедуры и функции. Связка слов была более полезной в моем конкретном случае, потому что она также учитывала «go» в последней строке.
Если вы не хотите использовать SMO (что лучше, чем решение ниже, но я хочу дать альтернативу ...), вы можете разделить свой запрос с помощью этой функции.
Это:
Строковое доказательство (пример печати 'no go')
private List<string> SplitScriptGo(string script)
{
var result = new List<string>();
int pos1 = 0;
int pos2 = 0;
bool whiteSpace = true;
bool emptyLine = true;
bool inStr = false;
bool inComment1 = false;
bool inComment2 = false;
while (true)
{
while (pos2 < script.Length && Char.IsWhiteSpace(script[pos2]))
{
if (script[pos2] == '\r' || script[pos2] == '\n')
{
emptyLine = true;
inComment1 = false;
}
pos2++;
}
if (pos2 == script.Length)
break;
bool min2 = (pos2 + 1) < script.Length;
bool min3 = (pos2 + 2) < script.Length;
if (!inStr && !inComment2 && min2 && script.Substring(pos2, 2) == "--")
inComment1 = true;
if (!inStr && !inComment1 && min2 && script.Substring(pos2, 2) == "/*")
inComment2 = true;
if (!inComment1 && !inComment2 && script[pos2] == '\'')
inStr = !inStr;
if (!inStr && !inComment1 && !inComment2 && emptyLine
&& (min2 && script.Substring(pos2, 2).ToLower() == "go")
&& (!min3 || char.IsWhiteSpace(script[pos2 + 2]) || script.Substring(pos2 + 2, 2) == "--" || script.Substring(pos2 + 2, 2) == "/*"))
{
if (!whiteSpace)
result.Add(script.Substring(pos1, pos2 - pos1));
whiteSpace = true;
emptyLine = false;
pos2 += 2;
pos1 = pos2;
}
else
{
pos2++;
whiteSpace = false;
if (!inComment2)
emptyLine = false;
}
if (!inStr && inComment2 && pos2 > 1 && script.Substring(pos2 - 2, 2) == "*/")
inComment2 = false;
}
if (!whiteSpace)
result.Add(script.Substring(pos1));
return result;
}
Если вы не хотите использовать SMO, например, потому что вам нужно быть кроссплатформенным, вы также можете использовать класс ScriptSplitter из SubText.
Вот реализация на C# и VB.NET
Использование:
string strSQL = @"
SELECT * FROM INFORMATION_SCHEMA.columns
GO
SELECT * FROM INFORMATION_SCHEMA.views
";
foreach(string Script in new Subtext.Scripting.ScriptSplitter(strSQL ))
{
Console.WriteLine(Script);
}
Если у вас есть проблемы с многострочными комментариями в стиле c, удалите комментарии с помощью регулярного выражения:
static string RemoveCstyleComments(string strInput)
{
string strPattern = @"/[*][\w\d\s]+[*]/";
//strPattern = @"/\*.*?\*/"; // Doesn't work
//strPattern = "/\*.*?\*/"; // Doesn't work
//strPattern = @"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/ "; // Doesn't work
//strPattern = @"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/ "; // Doesn't work
// http://stackoverflow.com/questions/462843/improving-fixing-a-regex-for-c-style-block-comments
strPattern = @"/\*(?>(?:(?>[^*]+)|\*(?!/))*)\*/"; // Works !
string strOutput = System.Text.RegularExpressions.Regex.Replace(strInput, strPattern, string.Empty, System.Text.RegularExpressions.RegexOptions.Multiline);
Console.WriteLine(strOutput);
return strOutput;
} // End Function RemoveCstyleComments
Удаление однострочных комментариев здесь:
https://stackoverflow.com/questions/9842991/regex-to-remove-single-line-sql-comments
этот класс рассматривает корпус /* Go */?
@cMinor: нет в разделителе, но вы можете удалить многострочные комментарии с помощью регулярного выражения, прежде чем разделить.
используйте следующий метод, чтобы разделить строку и выполнить пакет за пакетом
using System;
using System.IO;
using System.Text.RegularExpressions;
namespace RegExTrial
{
class Program
{
static void Main(string[] args)
{
string sql = String.Empty;
string path=@"D:\temp\sample.sql";
using (StreamReader reader = new StreamReader(path)) {
sql = reader.ReadToEnd();
}
//Select any GO (ignore case) that starts with at least
//one white space such as tab, space,new line, verticle tab etc
string pattern = "[\s](?i)GO(?-i)";
Regex matcher = new Regex(pattern, RegexOptions.Compiled);
int start = 0;
int end = 0;
Match batch=matcher.Match(sql);
while (batch.Success) {
end = batch.Index;
string batchQuery = sql.Substring(start, end - start).Trim();
//execute the batch
ExecuteBatch(batchQuery);
start = end + batch.Length;
batch = matcher.Match(sql,start);
}
}
private static void ExecuteBatch(string command)
{
//execute your query here
}
}
}
Я столкнулся с той же проблемой и в конце концов решил ее простой заменой строки, заменив слово GO точкой с запятой (;)
Кажется, все работает нормально при выполнении сценариев со встроенными комментариями, комментариями блоков и командами GO.
public static bool ExecuteExternalScript(string filePath)
{
using (StreamReader file = new StreamReader(filePath))
using (SqlConnection conn = new SqlConnection(dbConnStr))
{
StringBuilder sql = new StringBuilder();
string line;
while ((line = file.ReadLine()) != null)
{
// replace GO with semi-colon
if (line == "GO")
sql.Append(";");
// remove inline comments
else if (line.IndexOf("--") > -1)
sql.AppendFormat(" {0} ", line.Split(new string[] { "--" }, StringSplitOptions.None)[0]);
// just the line as it is
else
sql.AppendFormat(" {0} ", line);
}
conn.Open();
SqlCommand cmd = new SqlCommand(sql.ToString(), conn);
cmd.ExecuteNonQuery();
}
return true;
}
Это не сработает для команд DDL, которые должны быть в отдельном пакете. Например. создать / изменить таблицу и др.
Кроме того, похоже, вы удаляете комментарии без всякой причины .. Что, например, нарушит любые строки, содержащие --.
Привет, Blorgbeard - SQL Server 2012, похоже, нормально обрабатывает операторы DDL. Скрипты, которые я использовал, должны были позволить мне перестроить всю структуру базы данных, стереть текущую структуру, построить таблицы, добавить индексы и т. д. Я подумал; тоже закончил партию?
Кроме того, удаление комментариев было связано с тем, что это приведет к созданию одной строки SQL, поэтому любой SQL после комментария будет закомментирован, но я понимаю вашу точку зрения, если была строка, которая содержала - это не было комментарием.
Хорошо, только что посмотрел: «Операторы CREATE DEFAULT, CREATE FUNCTION, CREATE PROCEDURE, CREATE RULE, CREATE SCHEMA, CREATE TRIGGER и CREATE VIEW нельзя комбинировать с другими операторами в пакете. Оператор CREATE должен запускать пакет. Все». другие операторы, следующие в этом пакете, будут интерпретироваться как часть определения первого оператора CREATE. Невозможно изменить таблицу, а затем новые столбцы, указанные в том же пакете ».
Чтобы избежать посторонних, регулярных выражений, накладных расходов на память и быстрой работы с большими скриптами, я создал свой собственный потоковый парсер. Это
может распознавать комментарии с помощью - или / ** /
-- some commented text
/*
drop table Users;
GO
*/
может распознавать строковые литералы с 'или "
set @s =
'create table foo(...);
GO
create index ...';
и другие конструкции, такие как
gO -- commented text
try
{
using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Persist Security Info=True;Initial Catalog=DATABASE-NAME;Data Source=SERVER-NAME"))
{
connection.Open();
int rowsAffected = SqlStatementReader.ExecuteSqlFile(
"C:\\target-sql-script.sql",
connection,
// Don't forget to use the correct file encoding!!!
Encoding.Default,
// Indefinitely (sec)
0
);
}
}
// implement your handlers
catch (SqlStatementReader.SqlBadSyntaxException) { }
catch (SqlException) { }
catch (Exception) { }
class SqlStatementReader
{
public class SqlBadSyntaxException : Exception
{
public SqlBadSyntaxException(string description) : base(description) { }
public SqlBadSyntaxException(string description, int line) : base(OnBase(description, line, null)) { }
public SqlBadSyntaxException(string description, int line, string filePath) : base(OnBase(description, line, filePath)) { }
private static string OnBase(string description, int line, string filePath)
{
if (filePath == null)
return string.Format("Line: {0}. {1}", line, description);
else
return string.Format("File: {0}\r\nLine: {1}. {2}", filePath, line, description);
}
}
enum SqlScriptChunkTypes
{
InstructionOrUnquotedIdentifier = 0,
BracketIdentifier = 1,
QuotIdentifierOrLiteral = 2,
DblQuotIdentifierOrLiteral = 3,
CommentLine = 4,
CommentMultiline = 5,
}
StreamReader _sr = null;
string _filePath = null;
int _lineStart = 1;
int _lineEnd = 1;
bool _isNextChar = false;
char _nextChar = '\0';
public SqlStatementReader(StreamReader sr)
{
if (sr == null)
throw new ArgumentNullException("StreamReader can't be null.");
if (sr.BaseStream is FileStream)
_filePath = ((FileStream)sr.BaseStream).Name;
_sr = sr;
}
public SqlStatementReader(StreamReader sr, string filePath)
{
if (sr == null)
throw new ArgumentNullException("StreamReader can't be null.");
_sr = sr;
_filePath = filePath;
}
public int LineStart { get { return _lineStart; } }
public int LineEnd { get { return _lineEnd == 1 ? _lineEnd : _lineEnd - 1; } }
public void LightSyntaxCheck()
{
while (ReadStatementInternal(true) != null) ;
}
public string ReadStatement()
{
for (string s = ReadStatementInternal(false); s != null; s = ReadStatementInternal(false))
{
// skip empty
for (int i = 0; i < s.Length; i++)
{
switch (s[i])
{
case ' ': continue;
case '\t': continue;
case '\r': continue;
case '\n': continue;
default:
return s;
}
}
}
return null;
}
string ReadStatementInternal(bool syntaxCheck)
{
if (_isNextChar == false && _sr.EndOfStream)
return null;
StringBuilder allLines = new StringBuilder();
StringBuilder line = new StringBuilder();
SqlScriptChunkTypes nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
SqlScriptChunkTypes currentChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
char ch = '\0';
int lineCounter = 0;
int nextLine = 0;
int currentLine = 0;
bool nextCharHandled = false;
bool foundGO;
int go = 1;
while (ReadChar(out ch))
{
if (nextCharHandled == false)
{
currentChunk = nextChunk;
currentLine = nextLine;
switch (currentChunk)
{
case SqlScriptChunkTypes.InstructionOrUnquotedIdentifier:
if (ch == '[')
{
currentChunk = nextChunk = SqlScriptChunkTypes.BracketIdentifier;
currentLine = nextLine = lineCounter;
}
else if (ch == '"')
{
currentChunk = nextChunk = SqlScriptChunkTypes.DblQuotIdentifierOrLiteral;
currentLine = nextLine = lineCounter;
}
else if (ch == '\'')
{
currentChunk = nextChunk = SqlScriptChunkTypes.QuotIdentifierOrLiteral;
currentLine = nextLine = lineCounter;
}
else if (ch == '-' && (_isNextChar && _nextChar == '-'))
{
nextCharHandled = true;
currentChunk = nextChunk = SqlScriptChunkTypes.CommentLine;
currentLine = nextLine = lineCounter;
}
else if (ch == '/' && (_isNextChar && _nextChar == '*'))
{
nextCharHandled = true;
currentChunk = nextChunk = SqlScriptChunkTypes.CommentMultiline;
currentLine = nextLine = lineCounter;
}
else if (ch == ']')
{
throw new SqlBadSyntaxException("Incorrect syntax near ']'.", _lineEnd + lineCounter, _filePath);
}
else if (ch == '*' && (_isNextChar && _nextChar == '/'))
{
throw new SqlBadSyntaxException("Incorrect syntax near '*'.", _lineEnd + lineCounter, _filePath);
}
break;
case SqlScriptChunkTypes.CommentLine:
if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
{
nextCharHandled = true;
currentChunk = nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
currentLine = nextLine = lineCounter;
}
else if (ch == '\n' || ch == '\r')
{
currentChunk = nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
currentLine = nextLine = lineCounter;
}
break;
case SqlScriptChunkTypes.CommentMultiline:
if (ch == '*' && (_isNextChar && _nextChar == '/'))
{
nextCharHandled = true;
nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
nextLine = lineCounter;
}
else if (ch == '/' && (_isNextChar && _nextChar == '*'))
{
throw new SqlBadSyntaxException("Missing end comment mark '*/'.", _lineEnd + currentLine, _filePath);
}
break;
case SqlScriptChunkTypes.BracketIdentifier:
if (ch == ']')
{
nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
nextLine = lineCounter;
}
break;
case SqlScriptChunkTypes.DblQuotIdentifierOrLiteral:
if (ch == '"')
{
if (_isNextChar && _nextChar == '"')
{
nextCharHandled = true;
}
else
{
nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
nextLine = lineCounter;
}
}
break;
case SqlScriptChunkTypes.QuotIdentifierOrLiteral:
if (ch == '\'')
{
if (_isNextChar && _nextChar == '\'')
{
nextCharHandled = true;
}
else
{
nextChunk = SqlScriptChunkTypes.InstructionOrUnquotedIdentifier;
nextLine = lineCounter;
}
}
break;
}
}
else
nextCharHandled = false;
foundGO = false;
if (currentChunk == SqlScriptChunkTypes.InstructionOrUnquotedIdentifier || go >= 5 || (go == 4 && currentChunk == SqlScriptChunkTypes.CommentLine))
{
// go = 0 - break, 1 - begin of the string, 2 - spaces after begin of the string, 3 - G or g, 4 - O or o, 5 - spaces after GO, 6 - line comment after valid GO
switch (go)
{
case 0:
if (ch == '\r' || ch == '\n')
go = 1;
break;
case 1:
if (ch == ' ' || ch == '\t')
go = 2;
else if (ch == 'G' || ch == 'g')
go = 3;
else if (ch != '\n' && ch != '\r')
go = 0;
break;
case 2:
if (ch == 'G' || ch == 'g')
go = 3;
else if (ch == '\n' || ch == '\r')
go = 1;
else if (ch != ' ' && ch != '\t')
go = 0;
break;
case 3:
if (ch == 'O' || ch == 'o')
go = 4;
else if (ch == '\n' || ch == '\r')
go = 1;
else
go = 0;
break;
case 4:
if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
go = 5;
else if (ch == '\n' || ch == '\r')
foundGO = true;
else if (ch == ' ' || ch == '\t')
go = 5;
else if (ch == '-' && (_isNextChar && _nextChar == '-'))
go = 6;
else
go = 0;
break;
case 5:
if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
go = 5;
else if (ch == '\n' || ch == '\r')
foundGO = true;
else if (ch == '-' && (_isNextChar && _nextChar == '-'))
go = 6;
else if (ch != ' ' && ch != '\t')
throw new SqlBadSyntaxException("Incorrect syntax was encountered while parsing go.", _lineEnd + lineCounter, _filePath);
break;
case 6:
if (ch == '\r' && (_isNextChar && _nextChar == '\n'))
go = 6;
else if (ch == '\n' || ch == '\r')
foundGO = true;
break;
default:
go = 0;
break;
}
}
else
go = 0;
if (foundGO)
{
if (ch == '\r' || ch == '\n')
{
++lineCounter;
}
// clear GO
string s = line.Append(ch).ToString();
for (int i = 0; i < s.Length; i++)
{
switch (s[i])
{
case ' ': continue;
case '\t': continue;
case '\r': continue;
case '\n': continue;
default:
_lineStart = _lineEnd;
_lineEnd += lineCounter;
return allLines.Append(s.Substring(0, i)).ToString();
}
}
return string.Empty;
}
// accumulate by string
if (ch == '\r' && (_isNextChar == false || _nextChar != '\n'))
{
++lineCounter;
if (syntaxCheck == false)
allLines.Append(line.Append('\r').ToString());
line.Clear();
}
else if (ch == '\n')
{
++lineCounter;
if (syntaxCheck == false)
allLines.Append(line.Append('\n').ToString());
line.Clear();
}
else
{
if (syntaxCheck == false)
line.Append(ch);
}
}
// this is the end of the stream, return it without GO, if GO exists
switch (currentChunk)
{
case SqlScriptChunkTypes.InstructionOrUnquotedIdentifier:
case SqlScriptChunkTypes.CommentLine:
break;
case SqlScriptChunkTypes.CommentMultiline:
if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
throw new SqlBadSyntaxException("Missing end comment mark '*/'.", _lineEnd + currentLine, _filePath);
break;
case SqlScriptChunkTypes.BracketIdentifier:
if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
throw new SqlBadSyntaxException("Unclosed quotation mark [.", _lineEnd + currentLine, _filePath);
break;
case SqlScriptChunkTypes.DblQuotIdentifierOrLiteral:
if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
throw new SqlBadSyntaxException("Unclosed quotation mark \".", _lineEnd + currentLine, _filePath);
break;
case SqlScriptChunkTypes.QuotIdentifierOrLiteral:
if (nextChunk != SqlScriptChunkTypes.InstructionOrUnquotedIdentifier)
throw new SqlBadSyntaxException("Unclosed quotation mark '.", _lineEnd + currentLine, _filePath);
break;
}
if (go >= 4)
{
string s = line.ToString();
for (int i = 0; i < s.Length; i++)
{
switch (s[i])
{
case ' ': continue;
case '\t': continue;
case '\r': continue;
case '\n': continue;
default:
_lineStart = _lineEnd;
_lineEnd += lineCounter + 1;
return allLines.Append(s.Substring(0, i)).ToString();
}
}
}
_lineStart = _lineEnd;
_lineEnd += lineCounter + 1;
return allLines.Append(line.ToString()).ToString();
}
bool ReadChar(out char ch)
{
if (_isNextChar)
{
ch = _nextChar;
if (_sr.EndOfStream)
_isNextChar = false;
else
_nextChar = Convert.ToChar(_sr.Read());
return true;
}
else if (_sr.EndOfStream == false)
{
ch = Convert.ToChar(_sr.Read());
if (_sr.EndOfStream == false)
{
_isNextChar = true;
_nextChar = Convert.ToChar(_sr.Read());
}
return true;
}
else
{
ch = '\0';
return false;
}
}
public static int ExecuteSqlFile(string filePath, SqlConnection connection, Encoding fileEncoding, int commandTimeout)
{
int rowsAffected = 0;
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
// Simple syntax check (you can comment out these two lines below)
new SqlStatementReader(new StreamReader(fs, fileEncoding)).LightSyntaxCheck();
fs.Seek(0L, SeekOrigin.Begin);
// Read statements without GO
SqlStatementReader rd = new SqlStatementReader(new StreamReader(fs, fileEncoding));
string stmt;
while ((stmt = rd.ReadStatement()) != null)
{
using (SqlCommand cmd = connection.CreateCommand())
{
cmd.CommandText = stmt;
cmd.CommandTimeout = commandTimeout;
int i = cmd.ExecuteNonQuery();
if (i > 0)
rowsAffected += i;
}
}
}
return rowsAffected;
}
}
Я смотрю на это несколько раз, в конце решил с Реализация EF
Немного доработан для SqlConnection
public static void ExecuteSqlScript(this SqlConnection sqlConnection, string sqlBatch)
{
// Handle backslash utility statement (see http://technet.microsoft.com/en-us/library/dd207007.aspx)
sqlBatch = Regex.Replace(sqlBatch, @"\(\r\n|\r|\n)", string.Empty);
// Handle batch splitting utility statement (see http://technet.microsoft.com/en-us/library/ms188037.aspx)
var batches = Regex.Split(
sqlBatch,
string.Format(CultureInfo.InvariantCulture, @"^\s*({0}[ \t]+[0-9]+|{0})(?:\s+|$)", BatchTerminator),
RegexOptions.IgnoreCase | RegexOptions.Multiline);
for (int i = 0; i < batches.Length; ++i)
{
// Skip batches that merely contain the batch terminator
if (batches[i].StartsWith(BatchTerminator, StringComparison.OrdinalIgnoreCase) ||
(i == batches.Length - 1 && string.IsNullOrWhiteSpace(batches[i])))
{
continue;
}
// Include batch terminator if the next element is a batch terminator
if (batches.Length > i + 1 &&
batches[i + 1].StartsWith(BatchTerminator, StringComparison.OrdinalIgnoreCase))
{
int repeatCount = 1;
// Handle count parameter on the batch splitting utility statement
if (!string.Equals(batches[i + 1], BatchTerminator, StringComparison.OrdinalIgnoreCase))
{
repeatCount = int.Parse(Regex.Match(batches[i + 1], @"([0-9]+)").Value, CultureInfo.InvariantCulture);
}
for (int j = 0; j < repeatCount; ++j)
{
var command = sqlConnection.CreateCommand();
command.CommandText = batches[i];
command.ExecuteNonQuery();
}
}
else
{
var command = sqlConnection.CreateCommand();
command.CommandText = batches[i];
command.ExecuteNonQuery();
}
}
}
Спасибо @Filip Cordas. Хотя это не помечено как ответ, это помогло мне как шарм! У нас было большое количество скриптов, в которых BatchTerminator упоминался по-разному, например, комбинации верхнего и нижнего регистра (go, Go, GO и т. д.), А максимальное время имело конечные или ведущие пробелы с ним, что вызывало большую проблему для выполнения через C# ... . Спасибо !!
@DipakRiswadkar Да, заблокировал этот вопрос несколько раз, и ни один из предоставленных ответов не соответствует моим потребностям, поэтому посмотрел, как реализация EF выглядела хорошо, поэтому я опубликовал ответ.
Отличный ответ, работает как шарм, большое спасибо
@Really также должен сообщить об этом команде Entity Framework. Как я уже сказал, это всего лишь копия прошлого с небольшими изменениями.
Для тех, у кого все еще есть проблема. Вы можете использовать официальный Microsoft SMO
using (var connection = new SqlConnection(connectionString))
{
var server = new Server(new ServerConnection(connection));
server.ConnectionContext.ExecuteNonQuery(sql);
}
Это не добавляет ничего сверх принятого ответа, который также предлагает SMO (опубликовано 10 лет назад!).
Приятно, что при необходимости я могу легко адаптировать это к SqlCe - другой код использует классы и команды подключения Sql.