SSMS SMO对象:获取查询结果

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

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

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

最简单的事情可能只是打印你为ExecuteNonQuery获取的数字:

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

这应该有效,但不会SET NOCOUNT当前会话/范围的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命令的备注 。)

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

更新 :应该注意,这种方法(当然)要求你手动拆分GO分隔符的输入脚本/语句,因为你要回到使用SqlCommand.Execute*() ,它不能一次处理多个批处理。 为此,有多种选择:

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

我选择后面的选项,这是相当一些工作,因为它没有很好的文档和示例很少(谷歌有点,你会发现一些东西,或使用reflection器来看看SMO-Assemblies如何使用该类) 。

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

好吧,是那样做了你。 从关于如何打印“……受影响的行”的“简单问题”到以强大而通用的方式做事并不容易的事实(考虑到所需的后台工作)。 YMMV,祝你好运。

ManagedBatchParser用法的更新

关于如何实现IBatchSource似乎没有好的文档或示例,这就是我的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()返回的块,这样解析器仍然可以使用它们(看起来它们需要是完整的语句) ,这首先会破坏解析的目的……)。