SSMS
 sql >> Base de Dados >  >> Database Tools >> SSMS

Objetos SSMS SMO:obtenha resultados de consulta


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 como GO 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...).