La cosa più semplice è forse semplicemente stampare il numero che ottieni per ExecuteNonQuery
:
int rowsAffected = server.ConnectionContext.ExecuteNonQuery(/* ... */);
if (rowsAffected != -1)
{
Console.WriteLine("{0} rows affected.", rowsAffected);
}
Dovrebbe funzionare, ma non rispetterà il SET NOCOUNT
impostazione della sessione/ambito corrente.
Altrimenti lo faresti come faresti con ADO.NET "semplice". Non utilizzare ServerConnection.ExecuteNonQuery()
metodo, ma crea un SqlCommand
oggetto accedendo al sottostante SqlConnection
oggetto. Su quello iscriviti a StatementCompleted
evento.
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();
}
Utilizzo di StatementCompleted
(invece, diciamo, stampando manualmente il valore che ExecuteNonQuery()
restituito) ha il vantaggio di funzionare esattamente come SSMS o SQLCMD.EXE:
- Per i comandi che non hanno un ROWCOUNT non verrà chiamato affatto (es. GO, USE).
- Se
SET NOCOUNT ON
era impostato, non verrà chiamato affatto. - Se
SET NOCOUNT OFF
è stato impostato, verrà chiamato per ogni istruzione all'interno di un batch.
(Barra laterale:sembra StatementCompleted
è esattamente ciò di cui parla il protocollo TDS quando DONE_IN_PROC
l'evento è menzionato; vedere Osservazioni
del comando SET NOCOUNT su MSDN.)
Personalmente, ho utilizzato questo approccio con successo nel mio "clone" di SQLCMD.EXE.
AGGIORNAMENTO :Va notato che questo approccio (ovviamente) richiede di dividere manualmente lo script/le dichiarazioni di input in GO
separatore, perché sei tornato a usare SqlCommand.Execute*()
che non può gestire più batch alla volta. Per questo, ci sono più opzioni:
- Dividi manualmente l'input su righe che iniziano con
GO
(avvertenza:GO
può essere chiamato comeGO 5
, ad esempio, per eseguire il batch precedente 5 volte). - Utilizzare ManagedBatchParser class/library per aiutarti a suddividere l'input in singoli batch, in particolare implementare ICommandExecutor.ProcessBatch con il codice sopra (o qualcosa di simile).
Scelgo l'ultima opzione, che è stata un bel po' di lavoro, dato che non è abbastanza ben documentata e gli esempi sono rari (google un po', troverai alcune cose, o usa riflettore per vedere come gli SMO-Assemblies usano quella classe) .
Il vantaggio (e forse l'onere) dell'utilizzo di ManagedBatchParser
vale a dire che analizzerà anche tutti gli altri costrutti di script T-SQL (destinati a SQLCMD.EXE
) per te. Compreso::setvar
, :connect
, :quit
, ecc. Non è necessario implementare il rispettivo ICommandExecutor
membri, se i tuoi script non li usano, ovviamente. Ma ricorda che potresti non essere in grado di eseguire script "arbitrari".
Bene, dove ti ha messo? Dalla "semplice domanda" di come stampare "...righe interessate" al fatto che non è banale farlo in maniera robusta e generica (visto il lavoro di fondo richiesto). YMMV, buona fortuna.
Aggiornamento sull'utilizzo ManagedBatchParser
Non sembra esserci una buona documentazione o esempio su come implementare IBatchSource
, ecco cosa ho scelto.
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;
}
}
Ed ecco come lo useresti:
var source = new StatementBatchSource("SELECT GETUTCDATE()");
source.Populate();
var parser = new Parser();
parser.SetBatchSource(source);
/* other parser.Set*() calls */
parser.Parse();
Nota che entrambe le implementazioni, sia per le istruzioni dirette (StatementBatchSource
) o per un file (FileBatchSource
) hanno il problema di leggere subito il testo completo in memoria. Ho avuto un caso in cui è esploso, con un enorme (!) script con miliardi di INSERT
generati dichiarazioni. Anche se non penso che sia un problema pratico, SQLCMD.EXE
potrebbe gestirlo. Ma per la mia vita, non riuscivo a capire esattamente come, avresti bisogno di formare i blocchi restituiti per IBatchParser.GetContent()
in modo che il parser possa ancora lavorare con loro (sembra che dovrebbero essere dichiarazioni complete, il che vanificherebbe lo scopo dell'analisi in primo luogo...).