Nejjednodušší je vytisknout číslo, které dostanete zpět za ExecuteNonQuery
:
int rowsAffected = server.ConnectionContext.ExecuteNonQuery(/* ... */);
if (rowsAffected != -1)
{
Console.WriteLine("{0} rows affected.", rowsAffected);
}
To by mělo fungovat, ale nebude respektovat SET NOCOUNT
nastavení aktuální relace/rozsahu.
Jinak byste to udělali jako s "obyčejným" ADO.NET. Nepoužívejte ServerConnection.ExecuteNonQuery()
metodu, ale vytvořte SqlCommand
objekt přístupem k základnímu SqlConnection
objekt. Poté se přihlaste k odběru StatementCompleted
událost.
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();
}
Pomocí StatementCompleted
(místo toho, řekněme, ručně vytiskněte hodnotu, která ExecuteNonQuery()
return) má tu výhodu, že funguje přesně jako SSMS nebo SQLCMD.EXE:
- Příkazy, které nemají ŘÁDEK, nebudou volány vůbec (např. GO, USE).
- Pokud
SET NOCOUNT ON
byla nastavena, nebude volána vůbec. - Pokud
SET NOCOUNT OFF
byla nastavena, bude volána pro každý příkaz v dávce.
(Postranní panel:vypadá to jako StatementCompleted
je přesně to, o čem protokol TDS mluví, když DONE_IN_PROC
je zmíněna událost; viz Poznámky
příkazu SET NOCOUNT na MSDN.)
Osobně jsem tento přístup s úspěchem použil ve svém vlastním "klonu" SQLCMD.EXE.
AKTUALIZACE :Je třeba poznamenat, že tento přístup (samozřejmě) vyžaduje ruční rozdělení vstupního skriptu/příkazů na GO
oddělovač, protože jste se vrátili k používání SqlCommand.Execute*()
který nedokáže zpracovat více dávek najednou. K tomu existuje několik možností:
- Ručně rozdělte vstup na řádky začínající
GO
(upozornění:GO
lze nazvat jakoGO 5
, například pro provedení předchozí dávky 5krát). - Použijte ManagedBatchParser class/library, které vám pomohou rozdělit vstup do jednotlivých dávek, zejména implementovat ICommandExecutor.ProcessBatch s výše uvedeným kódem (nebo něčím podobným).
Vybral jsem si pozdější možnost, což byla docela práce, vzhledem k tomu, že to není dost dobře zdokumentované a příklady jsou vzácné (trochu googlujte, nějaké věci najdete, nebo použijte reflektor, abyste viděli, jak SMO-Assemblies tuto třídu používají) .
Výhoda (a možná i zátěž) používání ManagedBatchParser
spočívá v tom, že bude také analyzovat všechny ostatní konstrukce skriptů T-SQL (určeno pro SQLCMD.EXE
) pro tebe. Včetně::setvar
, :connect
, :quit
, atd. Nemusíte implementovat příslušný ICommandExecutor
členů, pokud je vaše skripty samozřejmě nepoužívají. Ale mějte na paměti, že možná nebudete moci spouštět „libovolné“ skripty.
No, to tě položilo. Od „jednoduché otázky“, jak vytisknout „... dotčené řádky“ až po skutečnost, že to není triviální dělat robustním a obecným způsobem (vzhledem k požadované práci na pozadí). YMMV, hodně štěstí.
Aktualizace používání ManagedBatchParser
Zdá se, že neexistuje žádná dobrá dokumentace nebo příklad o tom, jak implementovat IBatchSource
, zde je to, s čím jsem šel.
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;
}
}
A takto byste to použili:
var source = new StatementBatchSource("SELECT GETUTCDATE()");
source.Populate();
var parser = new Parser();
parser.SetBatchSource(source);
/* other parser.Set*() calls */
parser.Parse();
Všimněte si, že obě implementace, buď pro přímé příkazy (StatementBatchSource
) nebo pro soubor (FileBatchSource
) mají problém, že přečtou celý text najednou do paměti. Měl jsem jeden případ, kdy to prasklo, protože jsem měl obrovský (!) skript s miliony generovaných INSERT
prohlášení. I když si nemyslím, že to je praktický problém, SQLCMD.EXE
mohl to zvládnout. Ale za celý svůj život jsem nemohl přijít na to, jak přesně, budete muset vytvořit bloky vrácené pro IBatchParser.GetContent()
aby s nimi analyzátor mohl stále pracovat (vypadá to, že by to musely být úplné příkazy, což by v první řadě tak trochu mařilo účel analýzy...).