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

SSMS SMO-objecten:queryresultaten ophalen

Het gemakkelijkste is misschien om gewoon het nummer af te drukken dat u terugkrijgt voor ExecuteNonQuery :

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

Dit zou moeten werken, maar voldoet niet aan de SET NOCOUNT instelling van de huidige sessie/scope.

Anders zou je het doen zoals je zou doen met "gewone" ADO.NET. Gebruik niet de ServerConnection.ExecuteNonQuery() methode, maar maak een SqlCommand object door toegang te krijgen tot de onderliggende SqlConnection voorwerp. Schrijf u vervolgens in op de StatementCompleted evenement.

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

StatementCompleted gebruiken (in plaats daarvan, bijvoorbeeld, handmatig de waarde afdrukken die ExecuteNonQuery() geretourneerd) heeft het voordeel dat het precies werkt zoals SSMS of SQLCMD.EXE zou doen:

  • Voor opdrachten die geen ROWCOUNT hebben, wordt deze helemaal niet aangeroepen (bijv. GO, USE).
  • Als SET NOCOUNT ON is ingesteld, wordt het helemaal niet aangeroepen.
  • Als SET NOCOUNT OFF is ingesteld, wordt deze aangeroepen voor elke instructie in een batch.

(Zijbalk:het lijkt op StatementCompleted is precies waar het TDS-protocol over spreekt wanneer DONE_IN_PROC gebeurtenis wordt genoemd; zie Opmerkingen van de opdracht SET NOCOUNT op MSDN.)

Persoonlijk heb ik deze aanpak met succes gebruikt in mijn eigen "kloon" van SQLCMD.EXE.

UPDATE :Opgemerkt moet worden dat deze benadering (natuurlijk) vereist dat u het invoerscript/statements handmatig splitst op de GO scheidingsteken, omdat u weer SqlCommand.Execute*() gebruikt die niet meerdere batches tegelijk kan verwerken. Hiervoor zijn er meerdere opties:

  • Handmatig de invoer splitsen op regels die beginnen met GO (waarschuwing:GO kan worden aangeroepen als GO 5 , bijvoorbeeld om de vorige batch 5 keer uit te voeren).
  • Gebruik de ManagedBatchParser class/library om u te helpen de invoer in afzonderlijke batches te splitsen, vooral door ICommandExecutor.ProcessBatch met de bovenstaande code (of iets dat erop lijkt).

Ik koos voor de laatste optie, wat best wat werk was, aangezien het niet zo goed gedocumenteerd is en voorbeelden zeldzaam zijn (google een beetje, je zult wat dingen vinden, of gebruik reflector om te zien hoe de SMO-Assemblies die klasse gebruiken) .

Het voordeel (en misschien wel de last) van het gebruik van de ManagedBatchParser is dat het ook alle andere constructies van T-SQL-scripts zal parseren (bedoeld voor SQLCMD.EXE ) voor jou. Inclusief::setvar , :connect , :quit , etc. U hoeft de respectievelijke ICommandExecutor niet te implementeren leden, als uw scripts ze natuurlijk niet gebruiken. Houd er echter rekening mee dat u mogelijk geen "willekeurige" scripts kunt uitvoeren.

Nou, waar heb je dat gezet. Van de "eenvoudige vraag" hoe u "... getroffen rijen" moet afdrukken tot het feit dat het niet triviaal is om dit op een robuuste en algemene manier te doen (gezien het vereiste achtergrondwerk). YMMV, veel succes.

Update over het gebruik van ManagedBatchParser

Er lijkt geen goede documentatie of voorbeeld te zijn over het implementeren van IBatchSource , dit is waar ik mee ging.

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

En zo zou je het gebruiken:

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

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

parser.Parse();

Merk op dat beide implementaties, hetzij voor directe instructies (StatementBatchSource ) of voor een bestand (FileBatchSource ) hebben het probleem dat ze de volledige tekst in één keer in het geheugen lezen. Ik had een geval waarin dat opblies, met een enorm (!) script met ontelbare gegenereerde INSERT verklaringen. Ook al denk ik niet dat dit een praktisch probleem is, SQLCMD.EXE kon het aan. Maar voor het leven van mij, ik kon er niet achter komen hoe je precies de chunks zou moeten vormen die worden geretourneerd voor IBatchParser.GetContent() zodat de parser er nog steeds mee kan werken (het lijkt erop dat het volledige statements zouden moeten zijn, wat in de eerste plaats het doel van de parsering zou tenietdoen...).




  1. kan geen toegang krijgen tot sql-serverontwikkelaar sp1 ssis vanuit ssms

  2. SQL Azure:SMO-uitzondering bij het scripten van objecten in SSMS 2008 R2

  3. Hoe kan ik een andere functie aanroepen?

  4. Wij zijn sprekers op EclipseCon Europe 2018