sql >> Base de Datos >  >> Database Tools >> SSMS

Objetos SSMS SMO:Obtener resultados de consultas

Lo más fácil es posiblemente imprimir el número que obtienes para ExecuteNonQuery :

int rowsAffected = server.ConnectionContext.ExecuteNonQuery(/* ... */);
if (rowsAffected != -1)
{
     Console.WriteLine("{0} rows affected.", rowsAffected);
}

Esto debería funcionar, pero no respetará el SET NOCOUNT configuración de la sesión/ámbito actual.

De lo contrario, lo haría como lo haría con ADO.NET "simple". No use ServerConnection.ExecuteNonQuery() método, pero cree un SqlCommand objeto accediendo al SqlConnection subyacente objeto. En eso, suscríbase 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();
}

Usando StatementCompleted (en su lugar, digamos, imprimir manualmente el valor que ExecuteNonQuery() devuelto) tiene la ventaja de que funciona exactamente como SSMS o SQLCMD.EXE:

  • Para los comandos que no tienen ROWCOUNT, no se llamará en absoluto (por ejemplo, GO, USE).
  • Si SET NOCOUNT ON se configuró, no se llamará en absoluto.
  • Si SET NOCOUNT OFF se configuró, se llamará para cada declaración dentro de un lote.

(Barra lateral:parece StatementCompleted es exactamente de lo que habla el protocolo TDS cuando DONE_IN_PROC se menciona el evento; consulte Comentarios del comando SET NOCOUNT en MSDN.)

Personalmente, he usado este enfoque con éxito en mi propio "clon" de SQLCMD.EXE.

ACTUALIZAR :Debe tenerse en cuenta que este enfoque (por supuesto) requiere que usted divida manualmente el script/declaraciones de entrada en GO separador, porque ha vuelto a usar SqlCommand.Execute*() que no puede manejar múltiples lotes a la vez. Para ello, existen múltiples opciones:

  • Dividir manualmente la entrada en líneas que comienzan con GO (advertencia:GO se puede llamar como GO 5 , por ejemplo, para ejecutar el lote anterior 5 veces).
  • Utilice el ManagedBatchParser class/library para ayudarlo a dividir la entrada en lotes individuales, especialmente implemente ICommandExecutor.ProcessBatch con el código anterior (o algo parecido).

Elegí la última opción, que fue bastante trabajo, dado que no está muy bien documentada y los ejemplos son raros (busque un poco en Google, encontrará algunas cosas, o use reflector para ver cómo los SMO-Assemblies usan esa clase) .

El beneficio (y tal vez la carga) de usar el ManagedBatchParser es que también analizará todas las demás construcciones de scripts T-SQL (destinados a SQLCMD.EXE ) para usted. Incluyendo::setvar , :connect , :quit , etc. No tiene que implementar el respectivo ICommandExecutor miembros, si sus scripts no los usan, por supuesto. Pero tenga en cuenta que es posible que no pueda ejecutar secuencias de comandos "arbitrarias".

Bueno, ¿dónde te puso eso? Desde la "simple pregunta" de cómo imprimir "... filas afectadas" hasta el hecho de que no es trivial hacerlo de manera robusta y general (dado el trabajo de fondo requerido). YMMV, buena suerte.

Actualización sobre el uso de ManagedBatchParser

Parece que no hay una buena documentación o ejemplo sobre cómo implementar IBatchSource , esto es lo que hice.

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;
    }
}

Y así es como lo usarías:

var source = new StatementBatchSource("SELECT GETUTCDATE()");
source.Populate();

var parser = new Parser(); 
parser.SetBatchSource(source);
/* other parser.Set*() calls */

parser.Parse();

Tenga en cuenta que ambas implementaciones, ya sea para declaraciones directas (StatementBatchSource ) o para un archivo (FileBatchSource ) tienen el problema de que leen el texto completo de una vez en la memoria. Tuve un caso en el que explotó, tenía un script enorme (!) Con miles de millones de INSERT generados declaraciones. Aunque no creo que sea un problema práctico, SQLCMD.EXE podría manejarlo. Pero por mi vida, no pude averiguar cómo exactamente, necesitarías formar los fragmentos devueltos para IBatchParser.GetContent() para que el analizador aún pueda trabajar con ellos (parece que tendrían que ser declaraciones completas, lo que anularía el propósito del análisis en primer lugar...).