Най-лесното нещо е възможно просто да отпечатате номера, който получавате обратно за ExecuteNonQuery
:
int rowsAffected = server.ConnectionContext.ExecuteNonQuery(/* ... */);
if (rowsAffected != -1)
{
Console.WriteLine("{0} rows affected.", rowsAffected);
}
Това трябва да работи, но няма да зачита SET NOCOUNT
настройка на текущата сесия/обхват.
В противен случай бихте направили това, както бихте направили с "обикновения" ADO.NET. Не използвайте ServerConnection.ExecuteNonQuery()
метод, но създайте SqlCommand
обект чрез достъп до основния SqlConnection
обект. При това се абонирайте за StatementCompleted
събитие.
using (SqlCommand command = server.ConnectionContext.SqlConnectionObject.CreateCommand())
{
// Set other properties for "command", like StatementText, etc.
command.StatementCompleted += (s, e) => {
Console.WriteLine("{0} row(s) affected.", e.RecordCount);
};
command.ExecuteNonQuery();
}
Използване на StatementCompleted
(вместо, да речем, ръчно отпечатване на стойността, която ExecuteNonQuery()
върнати) има предимството, че работи точно както SSMS или SQLCMD.EXE:
- За команди, които нямат ROWCOUNT, тя изобщо няма да бъде извикана (напр. GO, USE).
- Ако
SET NOCOUNT ON
е зададен, той изобщо няма да бъде извикан. - Ако
SET NOCOUNT OFF
е зададен, той ще бъде извикан за всяко изявление в пакет.
(Странична лента:изглежда като StatementCompleted
е точно това, за което говори протоколът TDS, когато DONE_IN_PROC
събитие е споменато; вижте Забележки
на командата SET NOCOUNT в MSDN.)
Лично аз използвах този подход с успех в моя собствен „клонинг“ на SQLCMD.EXE.
АКТУАЛИЗИРАНЕ :Трябва да се отбележи, че този подход (разбира се) изисква ръчно да разделите входния скрипт/изявленията в GO
разделител, защото се връщате към използването на SqlCommand.Execute*()
който не може да обработва няколко партиди наведнъж. За това има няколко опции:
- Ръчно разделете въвеждането на редове, започващи с
GO
(предупреждение:GO
може да се извика катоGO 5
, например, за да изпълните предишната партида 5 пъти). - Използвайте ManagedBatchParser class/library, за да ви помогне да разделите входа на единични пакети, особено внедрете ICommandExecutor.ProcessBatch с кода по-горе (или нещо подобно).
Избирам по-късната опция, която беше доста работа, като се има предвид, че не е много добре документирана и примерите са рядкост (погугъл малко, ще намерите някои неща или използвайте рефлектор, за да видите как SMO-Assemblies използват този клас) .
Ползата (и може би тежестта) от използването на ManagedBatchParser
е, че също така ще анализира всички други конструкции на T-SQL скриптове (предназначени за SQLCMD.EXE
) за теб. Включително::setvar
, :connect
, :quit
и т.н. Не е нужно да внедрявате съответния ICommandExecutor
членове, ако вашите скриптове не ги използват, разбира се. Но имайте предвид, че може да не сте в състояние да изпълнявате „произволни“ скриптове.
Е, дали това те постави. От „простия въпрос“ как да отпечатате „... засегнати редове“ до факта, че не е тривиално да се направи по стабилен и общ начин (като се има предвид необходимата фонова работа). YMMV, успех.
Актуализация относно използването на ManagedBatchParser
Изглежда, че няма добра документация или пример за това как да се внедри IBatchSource
, ето какво минах.
internal abstract class BatchSource : IBatchSource
{
private string m_content;
public void Populate()
{
m_content = GetContent();
}
public void Reset()
{
m_content = null;
}
protected abstract string GetContent();
public ParserAction GetMoreData(ref string str)
{
str = null;
if (m_content != null)
{
str = m_content;
m_content = null;
}
return ParserAction.Continue;
}
}
internal class FileBatchSource : BatchSource
{
private readonly string m_fileName;
public FileBatchSource(string fileName)
{
m_fileName = fileName;
}
protected override string GetContent()
{
return File.ReadAllText(m_fileName);
}
}
internal class StatementBatchSource : BatchSource
{
private readonly string m_statement;
public StatementBatchSource(string statement)
{
m_statement = statement;
}
protected override string GetContent()
{
return m_statement;
}
}
И ето как бихте го използвали:
var source = new StatementBatchSource("SELECT GETUTCDATE()");
source.Populate();
var parser = new Parser();
parser.SetBatchSource(source);
/* other parser.Set*() calls */
parser.Parse();
Обърнете внимание, че и двете реализации, или за директни изрази (StatementBatchSource
) или за файл (FileBatchSource
) имат проблем, че четат целия текст наведнъж в паметта. Имах един случай, когато това се взриви, имайки огромен(!) скрипт с милиони генерирани INSERT
изявления. Въпреки че не мисля, че това е практически проблем, SQLCMD.EXE
можеше да се справи. Но за живота на мен не можах да разбера как точно, ще трябва да формирате късовете, върнати за IBatchParser.GetContent()
така че синтактичният анализатор все още може да работи с тях (изглежда, че те ще трябва да бъдат пълни изявления, което на първо място ще попречи на целта на синтактичния анализ...).