A coisa mais fácil é simplesmente imprimir o número que você recebe de volta para
ExecuteNonQuery
:int rowsAffected = server.ConnectionContext.ExecuteNonQuery(/* ... */);
if (rowsAffected != -1)
{
Console.WriteLine("{0} rows affected.", rowsAffected);
}
Isso deve funcionar, mas não respeitará o
SET NOCOUNT
configuração da sessão/escopo atual. Caso contrário, você faria como faria com o ADO.NET "simples". Não use o
ServerConnection.ExecuteNonQuery()
método, mas crie um SqlCommand
objeto acessando o SqlConnection
subjacente objeto. Nesse subscreva o 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();
}
Usando
StatementCompleted
(em vez disso, digamos, imprimir manualmente o valor que ExecuteNonQuery()
retornado) tem o benefício de funcionar exatamente como o SSMS ou SQLCMD.EXE faria:- Para comandos que não possuem um ROWCOUNT, ele não será chamado (por exemplo, GO, USE).
- Se
SET NOCOUNT ON
foi definido, ele não será chamado. - Se
SET NOCOUNT OFF
foi definido, ele será chamado para cada instrução dentro de um lote.
(Barra lateral:parece
StatementCompleted
é exatamente o que o protocolo TDS fala quando DONE_IN_PROC
evento é mencionado; consulte Observações
do comando SET NOCOUNT no MSDN.) Pessoalmente, usei essa abordagem com sucesso em meu próprio "clone" do SQLCMD.EXE.
ATUALIZAÇÃO :Deve-se notar que esta abordagem (é claro) requer que você divida manualmente o script/instruções de entrada no
GO
separador, porque você voltou a usar SqlCommand.Execute*()
que não pode lidar com vários lotes ao mesmo tempo. Para isso, existem várias opções:- Divida manualmente a entrada em linhas que começam com
GO
(aviso:GO
pode ser chamado comoGO 5
, por exemplo, para executar o lote anterior 5 vezes). - Use o ManagedBatchParser class/library para ajudá-lo a dividir a entrada em lotes únicos, especialmente implementar ICommandExecutor.ProcessBatch com o código acima (ou algo parecido).
Eu escolho a opção posterior, que foi bastante trabalhosa, já que não está muito bem documentada e os exemplos são raros (google um pouco, você encontrará algumas coisas, ou use reflector para ver como os SMO-Assemblies usam essa classe) .
O benefício (e talvez o ônus) de usar o
ManagedBatchParser
é, que também irá analisar todas as outras construções de scripts T-SQL (destinados a SQLCMD.EXE
) para você. Incluindo::setvar
, :connect
, :quit
, etc. Você não precisa implementar o respectivo ICommandExecutor
membros, se seus scripts não os usarem, é claro. Mas lembre-se de que talvez você não consiga executar scripts "arbitrários". Bem, foi isso que colocou você. Da "simples questão" de como imprimir "... linhas afetadas" ao fato de que não é trivial fazê-lo de maneira robusta e geral (dado o trabalho de fundo necessário). YMMV, boa sorte.
Atualização no uso do ManagedBatchParser
Parece não haver boa documentação ou exemplo sobre como implementar
IBatchSource
, aqui está o que eu fui com. 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;
}
}
E é assim que você usaria:
var source = new StatementBatchSource("SELECT GETUTCDATE()");
source.Populate();
var parser = new Parser();
parser.SetBatchSource(source);
/* other parser.Set*() calls */
parser.Parse();
Observe que ambas as implementações, tanto para instruções diretas (
StatementBatchSource
) ou para um arquivo (FileBatchSource
) têm o problema de ler o texto completo de uma só vez na memória. Eu tive um caso em que isso explodiu, tendo um script enorme (!) com zilhões de INSERT
gerados declarações. Mesmo que eu não ache que isso seja um problema prático, SQLCMD.EXE
poderia lidar com isso. Mas, para minha vida, não consegui descobrir exatamente como você precisaria formar os pedaços retornados para IBatchParser.GetContent()
para que o analisador ainda possa trabalhar com eles (parece que eles precisariam ser declarações completas, o que meio que anularia o propósito da análise em primeiro lugar...).