SSMS SMO对象:获取查询结果

时间:2012-08-28 07:00:56

标签: c# sql-server ado.net ssms smo

我遇到了this教程,了解如何使用GO语句执行SQL脚本 现在我想知道我能得到消息TAB的输出。

使用几个GO语句,输出将如下:
1行受影响 受影响的912行
......

但是server.ConnectionContext.ExecuteNonQuery()只能返回一个int,而我需要所有的文本。如果查询的某些部分存在某些错误,则应将其也放在输出中。 任何帮助,将不胜感激。

1 个答案:

答案 0 :(得分:4)

最简单的方法是打印ExecuteNonQuery获取的数字:

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

这应该有效,但不会遵守当前会话/范围的SET NOCOUNT设置。

否则你会像使用“普通”ADO.NET那样做。不要使用ServerConnection.ExecuteNonQuery()方法,而是通过访问基础SqlCommand对象来创建SqlConnection对象。订阅StatementCompleted事件。

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(相反,比方说,手动打印ExecuteNonQuery()返回的值)具有与SSMS或SQLCMD.EXE完全相同的优点:

  • 对于没有ROWCOUNT的命令,根本不会调用它(例如GO,USE)。
  • 如果设置了SET NOCOUNT ON,则根本不会调用它。
  • 如果设置了SET NOCOUNT OFF,则会为批处理中的每个语句调用它。

(补充工具栏:看起来StatementCompleted正是TDS协议在提到DONE_IN_PROC事件时所说的内容;请参阅MSDN上的SET NOCOUNT命令的Remarks。)

就我个人而言,我已经在我自己的“克隆”SQLCMD.EXE中成功使用了这种方法。

更新:需要注意的是,这种方法(当然)要求您在GO分隔符处手动拆分输入脚本/语句,因为您已经恢复使用SqlCommand.Execute*(),一次无法处理多个批次。为此,有多种选择:

  • GO开头的行手动拆分输入(警告:GO可以像GO 5一样调用,例如,执行前一批次5次。)
  • 使用ManagedBatchParser类/库帮助您将输入分成单个批次,尤其是使用上面的代码(或类似的代码)实现ICommandExecutor.ProcessBatch

我选择了后面的选项,这是相当多的工作,因为它没有很好的文档和示例很少(谷歌有点,你会找到一些东西,或使用反射器来看看SMO组件如何使用那个班。)

使用ManagedBatchParser的好处(也许是负担)是,它还将为您解析T-SQL脚本的所有其他构造(适用于SQLCMD.EXE)。包括::setvar:connect:quit等。当然,如果您的脚本不使用它们,则不必实现相应的ICommandExecutor成员。但请注意,您可能无法执行“任意”脚本。

嗯,是不是这样做了。从关于如何打印“......受影响的行”的“简单问题”到以强大和一般的方式做事并不是微不足道的事实(考虑到所需的背景工作)。 YMMV,祝你好运。

有关ManagedBatchParser使用情况的更新

似乎没有关于如何实施IBatchSource的好文件或示例,这就是我的目标。

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

这就是你如何使用它:

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

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

parser.Parse();

请注意,直接语句(StatementBatchSource)或文件(FileBatchSource)的这两种实现都存在一个问题,即它们会立即读取完整的文本 进入记忆。我有一个案例爆炸了,有一个巨大的(!)脚本与生成的INSERT语句的gazillions。即使我认为这不是一个实际问题,SQLCMD.EXE也可以处理它。但对于我的生活,我无法弄清楚究竟是怎样的, 你需要形成为IBatchParser.GetContent()返回的块,以便... 解析器仍然可以使用它们(看起来它们需要是完整的语句, 这首先会破坏解析的目的......)。