SSMS
 sql >> Datenbank >  >> Database Tools >> SSMS

SSMS-SMO-Objekte:Abrufen von Abfrageergebnissen

Am einfachsten ist es vielleicht, einfach die Zahl auszudrucken, die man für ExecuteNonQuery zurückbekommt :

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

Dies sollte funktionieren, wird aber SET NOCOUNT nicht berücksichtigen Einstellung der aktuellen Sitzung/Bereich.

Ansonsten würden Sie es genauso machen wie mit dem „einfachen“ ADO.NET. Verwenden Sie nicht ServerConnection.ExecuteNonQuery() -Methode, aber erstellen Sie einen SqlCommand -Objekt, indem Sie auf die zugrunde liegende SqlConnection zugreifen Objekt. Abonnieren Sie darauf das StatementCompleted Veranstaltung.

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

Verwenden von StatementCompleted (sagen wir stattdessen, den Wert manuell auszugeben, den ExecuteNonQuery() zurückgegeben) hat den Vorteil, dass es genauso funktioniert wie SSMS oder SQLCMD.EXE:

  • Für Befehle, die kein ROWCOUNT haben, wird es überhaupt nicht aufgerufen (z. B. GO, USE).
  • Wenn SET NOCOUNT ON eingestellt wurde, wird es gar nicht aufgerufen.
  • Falls SET NOCOUNT OFF gesetzt wurde, wird es für jede Anweisung innerhalb eines Stapels aufgerufen.

(Seitenleiste:Es sieht aus wie StatementCompleted ist genau das, worüber das TDS-Protokoll spricht, wenn DONE_IN_PROC Ereignis wird erwähnt; siehe Bemerkungen des SET NOCOUNT-Befehls auf MSDN.)

Ich persönlich habe diesen Ansatz erfolgreich in meinem eigenen "Klon" von SQLCMD.EXE verwendet.

AKTUALISIEREN :Es sollte beachtet werden, dass dieser Ansatz (natürlich) erfordert, dass Sie das Eingabeskript/die Anweisungen beim GO manuell aufteilen Trennzeichen, da Sie wieder SqlCommand.Execute*() verwenden die nicht mehrere Chargen gleichzeitig verarbeiten kann. Dafür gibt es mehrere Möglichkeiten:

  • Teilen Sie die Eingabe manuell in Zeilen auf, die mit GO beginnen (Achtung:GO kann aufgerufen werden wie GO 5 , um beispielsweise den vorherigen Stapel fünfmal auszuführen).
  • Verwenden Sie den ManagedBatchParser class/library, um Ihnen zu helfen, die Eingabe in einzelne Batches aufzuteilen, insbesondere implementieren Sie ICommandExecutor.ProcessBatch mit dem obigen Code (oder etwas Ähnlichem).

Ich wähle die spätere Option, die ziemlich viel Arbeit war, da sie nicht ziemlich gut dokumentiert ist und Beispiele selten sind (googeln Sie ein bisschen, Sie werden einiges finden, oder verwenden Sie den Reflektor, um zu sehen, wie die SMO-Assemblies diese Klasse verwenden). .

Der Vorteil (und vielleicht auch die Belastung) der Verwendung des ManagedBatchParser ist, dass es auch alle anderen Konstrukte von T-SQL-Skripten parsen wird (vorgesehen für SQLCMD.EXE ) für dich. Einschließlich::setvar , :connect , :quit , usw. Sie müssen den entsprechenden ICommandExecutor nicht implementieren Mitglieder, wenn Ihre Skripte sie natürlich nicht verwenden. Beachten Sie jedoch, dass Sie möglicherweise keine "beliebigen" Skripte ausführen können.

Wohin hat dich das gebracht? Von der "einfachen Frage", wie man "... betroffene Zeilen" druckt, bis hin zur Tatsache, dass es nicht trivial ist, dies auf robuste und allgemeine Weise zu tun (angesichts der erforderlichen Hintergrundarbeit). YMMV, viel Glück.

Update zur ManagedBatchParser-Nutzung

Es scheint keine gute Dokumentation oder Beispiel zur Implementierung von IBatchSource zu geben , hier ist, was ich genommen habe.

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

Und so würden Sie es verwenden:

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

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

parser.Parse();

Beachten Sie, dass beide Implementierungen, entweder für direkte Anweisungen (StatementBatchSource ) oder für eine Datei (FileBatchSource ) haben das Problem, dass sie sich den kompletten Text auf einmal ins Gedächtnis einlesen. Ich hatte einen Fall, in dem das explodierte, da ich ein riesiges (!) Skript mit Millionen von generierten INSERT hatte Aussagen. Auch wenn ich nicht denke, dass dies ein praktisches Problem ist, SQLCMD.EXE konnte damit umgehen. Aber für mein Leben konnte ich nicht herausfinden, wie genau Sie die Chunks bilden müssten, die für IBatchParser.GetContent() zurückgegeben werden damit der Parser immer noch mit ihnen arbeiten kann (es sieht so aus, als müssten sie vollständige Anweisungen sein, was den Zweck der Analyse von vornherein zunichte machen würde ...).