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()
返回的块,这样解析器仍然可以使用它们(看起来它们需要是完整的语句) ,这首先会破坏解析的目的……)。