SSMS
 sql >> Base de données >  >> Database Tools >> SSMS

Objets SSMS SMO :obtenir les résultats de la requête

La chose la plus simple est peut-être simplement d'imprimer le numéro que vous obtenez en retour pour ExecuteNonQuery :

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

Cela devrait fonctionner, mais ne respectera pas le SET NOCOUNT réglage de la session/portée en cours.

Sinon, vous le feriez comme vous le feriez avec ADO.NET "simple". N'utilisez pas le ServerConnection.ExecuteNonQuery() méthode, mais créez un SqlCommand objet en accédant au SqlConnection sous-jacent objet. Sur ce, abonnez-vous au StatementCompleted événement.

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

Utilisation de StatementCompleted (au lieu, disons, d'imprimer manuellement la valeur que ExecuteNonQuery() renvoyé) a l'avantage de fonctionner exactement comme SSMS ou SQLCMD.EXE :

  • Pour les commandes qui n'ont pas de ROWCOUNT, elles ne seront pas appelées du tout (par exemple, GO, USE).
  • Si SET NOCOUNT ON a été défini, il ne sera pas appelé du tout.
  • Si SET NOCOUNT OFF a été défini, il sera appelé pour chaque instruction à l'intérieur d'un lot.

(Encadré :il ressemble à StatementCompleted est exactement ce dont parle le protocole TDS lorsque DONE_IN_PROC l'événement est mentionné ; voir Remarques de la commande SET NOCOUNT sur MSDN.)

Personnellement, j'ai utilisé cette approche avec succès dans mon propre "clone" de SQLCMD.EXE.

MISE À JOUR  :Il convient de noter que cette approche (bien sûr) vous oblige à diviser manuellement le script d'entrée/les instructions au niveau du GO séparateur, car vous utilisez de nouveau SqlCommand.Execute*() qui ne peut pas gérer plusieurs lots à la fois. Pour cela, plusieurs options s'offrent à vous :

  • Diviser manuellement l'entrée sur les lignes commençant par GO (avertissement :GO peut être appelé comme GO 5 , par exemple, pour exécuter le lot précédent 5 fois).
  • Utilisez ManagedBatchParser class/library pour vous aider à diviser l'entrée en lots uniques, en particulier implémentez ICommandExecutor.ProcessBatch avec le code ci-dessus (ou quelque chose qui y ressemble).

J'ai choisi l'option la plus récente, qui était assez laborieuse, étant donné qu'elle n'est pas assez bien documentée et que les exemples sont rares (google un peu, vous trouverez des trucs, ou utilisez un réflecteur pour voir comment les SMO-Assemblies utilisent cette classe) .

L'avantage (et peut-être le fardeau) de l'utilisation de ManagedBatchParser c'est-à-dire qu'il analysera également toutes les autres constructions de scripts T-SQL (destinés à SQLCMD.EXE ) pour toi. Dont ::setvar , :connect , :quit , etc. Vous n'avez pas besoin d'implémenter le ICommandExecutor respectif membres, si vos scripts ne les utilisent pas, bien sûr. Mais n'oubliez pas que vous ne pourrez peut-être pas exécuter de scripts "arbitraires".

Eh bien, où est-ce que ça t'a mis. De la "simple question" de savoir comment imprimer "... les lignes concernées" au fait que ce n'est pas anodin de le faire de manière robuste et générale (vu le travail de fond nécessaire). YMMV, bonne chance.

Mise à jour sur l'utilisation de ManagedBatchParser

Il ne semble pas y avoir de bonne documentation ou d'exemple sur la façon d'implémenter IBatchSource , voici ce que j'ai choisi.

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

Et voici comment vous l'utiliseriez :

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

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

parser.Parse();

Notez que les deux implémentations, soit pour les instructions directes (StatementBatchSource ) ou pour un fichier (FileBatchSource ) ont le problème qu'ils lisent le texte complet à la fois dans la mémoire. J'ai eu un cas où cela a explosé, ayant un script énorme (!) Avec des millions de INSERT générés déclarations. Même si je ne pense pas que ce soit un problème pratique, SQLCMD.EXE pourrait le gérer. Mais pour ma vie, je ne pouvais pas comprendre exactement comment, vous auriez besoin de former les morceaux renvoyés pour IBatchParser.GetContent() afin que l'analyseur puisse toujours travailler avec eux (il semble qu'ils devraient être des instructions complètes, ce qui irait à l'encontre de l'objectif de l'analyse en premier lieu...).