SSMS SMO对象:获取查询结果

4

我看到了这个教程,以了解如何使用GO语句执行SQL脚本。
现在我想知道如何获取消息TAB的输出。

对于多个GO语句,输出将会是这样的:
1 行受影响
912 行受影响
...

但是server.ConnectionContext.ExecuteNonQuery()只能返回一个整数,而我需要所有的文本。如果查询中有错误,它也应该将其放入输出中。 非常感谢任何帮助。

1个回答

4
最简单的方法可能就是直接打印ExecuteNonQuery返回的数字:
int rowsAffected = server.ConnectionContext.ExecuteNonQuery(/* ... */);
if (rowsAffected != -1)
{
     Console.WriteLine("{0} rows affected.", rowsAffected);
}

这应该可以工作,但不会遵守当前会话/范围的SET NOCOUNT设置。
否则,您应该像使用“普通”ADO.NET一样进行操作。不要使用ServerConnection.ExecuteNonQuery()方法,而是通过访问基础SqlConnection对象创建一个SqlCommand对象。在其中订阅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-Assemblies 如何使用该类)。

使用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语句,导致内存崩溃。虽然我不认为这是一个实际问题,但SQLCMD.EXE可以处理。但我却一直搞不清楚应该如何形成返回给IBatchParser.GetContent()的块,以便解析器仍然可以使用它们(看起来它们需要是完整的语句,这在第一次解析时可能会失去意义...)。

它很好,但是SQL脚本中有GO关键字,如果我使用普通的ADO.NET,它会显示错误,我可以将它们拆分为字符串使用GO,但仍然存在问题。有没有可能以我想要的方式完成它? - Akshay J
问题在于,“你想要的方式”可能并不正确(在所有情况下)。请参见我在答案顶部的更新。 - Christian.K
@Christian.K,你在哪里找到有关使用ManagedBatchParser的信息或文档?我找不到IBatchSource的实现。它需要自己制作吗? - JJS
@JJS 当时我也找不到任何实质性的信息。我做了以下几点:在我所知道的程序集中进行dotPeek探索(例如SSMS的部分),必须使用解析器,并第二次阅读文档,用 SQLCMD.EXE 进行测试。后者特别是针对 ICommandExecutorEdListVarServerList 等方法以及 IVariableResolver 方法(类似于 SQLCMD.EXE 中的 :setvar 等)。关于 IBatchSource 的实现,我会更新我的答案。 - Christian.K
@Christian.K 感谢您提供的好建议。看起来微软确实有一个 IBatchSource 的实现!请查看 SQL 2008 或 2012 中的 GAC 中的 Microsoft.SqlServer.BatchParserClient!我想在您之前将其规范化。如何将大型 SQL 脚本解析为批处理? - JJS
@JJS 很好的发现。不过我对“大”的部分有些怀疑。我在你的答案下留了评论。 - Christian.K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接