4

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

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

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

4

1 回答 1

4

最简单的事情可能是只打印您返回的号码ExecuteNonQuery

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

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

否则,您将像使用“普通”ADO.NET 一样进行操作。不要使用方法,而是通过访问底层对象来ServerConnection.ExecuteNonQuery()创建对象。在那订阅事件。SqlCommandSqlConnectionStatementCompleted

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手动拆分以(caveat:开头的行的输入GOGO 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()以便解析器仍然可以使用它们(看起来它们需要是完整的语句,这有点像首先击败解析的目的......)。

于 2012-08-28T07:08:28.657 回答