在 SQL 为 运行 时在 .NET 中捕获 SQL PRINT 输出

Capture SQL PRINT output in .NET while the SQL is running

基本就是这样question.

建议的方法引发 一个 事件,无论打印数量如何,我想改为引发 一个 .Net 事件为每个打印 在 SQL 代码中。

想法是向用户发送有关长 运行 存储过程进度的通知。

并不是 InfoMessage 事件只触发一次,而是 SQL 服务器不会立即发送 PRINT 输出。您可以尝试将 RAISERROR 与 NOWAIT 选项一起使用;

RAISERROR ('My message', 0, 1) WITH NOWAIT

使用严重性低于 11 的 RAISERROR 不被视为错误条件。

在 .Net 中您需要设置 SqlConnection.FireInfoMessageEventOnUserErrors = true 以立即接收消息。此设置的结果是 SQL 错误将不再引发异常,它们将是信息消息,因此您必须检查 InfoMessage 事件处理程序中的真实错误。

编辑:完整的工作示例

我整理了一个完整的工作示例。我是 C# 人,不是 VB 人,对此我深表歉意。确保您阅读了连接的 FireInfoMessageEventOnUserErrors 属性。这是我的简单 SP;

编辑 2:增强工作示例

我在 InfoMessage 事件处理程序中添加了对真实错误的检查,并添加了一个 SqlCommand.StatementCompleted 事件处理程序来显示 'X rows affected' 消息如何适应。我在中添加了更多消息演示 SP,因此可以更轻松地查看哪些消息被延迟,哪些消息被及时接收。

create proc dbo.MyProc as
begin
    declare @t table (i int)
    set nocount off
    print 'This is the 1st PRINT message.'
    waitfor delay '00:00:01'
    -- generate a 'rows affected' info message.
    insert @t values (1)
    waitfor delay '00:00:01'
    print 'This is the 2nd PRINT message.'
    waitfor delay '00:00:01'
    insert @t values (1)
    waitfor delay '00:00:01'
    raiserror('This is the 1st RAISERROR NOWAIT message, severity 1.', 1, 1) with nowait
    waitfor delay '00:00:01'
    insert @t values (1)
    waitfor delay '00:00:01'
    print 'This is the 3rd PRINT message.'
    waitfor delay '00:00:01'
    raiserror('This is the 2nd RAISERROR NOWAIT message, severity 10.', 10, 1) with nowait
    waitfor delay '00:00:01'
    -- generate an error message.
    raiserror('This is the 3rd RAISERROR NOWAIT message, severity 11.', 11, 1) with nowait
    waitfor delay '00:00:01'
    print 'This is the 4th PRINT message.'
    waitfor delay '00:00:01'
    -- generate a single real error.
    declare @i int
    select @i = 1 / 0
    waitfor delay '00:00:01'
    print 'This is the 5th PRINT message.'
    waitfor delay '00:00:01'
    insert @t values (1)
    waitfor delay '00:00:01'
    -- generate multiple real errors from one statement.
    backup log fakedb to disk = 'xxx'
    waitfor delay '00:00:01'
    print 'This is the 6th PRINT message.'
    waitfor delay '00:00:01'
    raiserror('This is the final RAISERROR NOWAIT message, severity 10.', 10, 1) with nowait
    waitfor delay '00:00:01'
    print 'This is the final PRINT message.'
    waitfor delay '00:00:01'
end

这是我的简单控制台应用程序;

namespace RaiserrorNoWait
{
    using System;
    using System.Data;
    using System.Data.SqlClient;

    static class Program
    {
        static void Main()
        {
            using (SqlConnection connection = new SqlConnection(@"Integrated Security=SSPI;Initial Catalog=Scratch;Data Source=DESKTOP-DIR91LS\MSSQL2016"))
            {
                connection.InfoMessage += ConnectionInfoMessage;
                connection.FireInfoMessageEventOnUserErrors = true;
                connection.Open();

                using (SqlCommand command = new SqlCommand("dbo.MyProc", connection))
                {
                    command.CommandType = CommandType.StoredProcedure;
                    Console.WriteLine("{0:T} - Execute query...", DateTime.Now);
                    command.StatementCompleted += CommandStatementCompleted;
                    command.ExecuteNonQuery();
                    Console.WriteLine("{0:T} - Query finished.", DateTime.Now);
                }
                connection.Close();
            }
            Console.WriteLine("Press Enter to Exit.");
            Console.ReadLine();
        }

        // StatementCompleted events and InfoMessage events might not fire in the expected order although 
        // in my very limited testing with SQL Server 2016 and .Net Framework 4.8 they appear to.
        // See https://dba.stackexchange.com/questions/119334/how-can-i-get-individual-rowcounts-like-ssms

        private static void CommandStatementCompleted(object sender, StatementCompletedEventArgs e)
        {
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("{0:T} - StatementCompleted: {1} rows affected", DateTime.Now, e.RecordCount);
        }

        private static void ConnectionInfoMessage(object sender, SqlInfoMessageEventArgs e)
        {
            // The documentation states that e.Errors will always have at least one SqlError.
            // See https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlerrorcollection?view=dotnet-plat-ext-3.1

            // We could assume that when FireInfoMessageEventOnUserErrors is true then the event will be raised once per error
            // but the documentation does not clearly state this so we have to check each SqlError to see if it is a real error. 

            bool isRealError = false;
            byte maxSeverity = 0;
            foreach (SqlError sqlError in e.Errors)
            {
                isRealError = isRealError || sqlError.Class > 10;
                maxSeverity = Math.Max(maxSeverity, sqlError.Class);
            }

            // Here we display errors in red but we could throw an exception or perform some error handling.

            string messageType = isRealError ? "Error" : "Info";
            Console.ForegroundColor = isRealError ? ConsoleColor.Red : ConsoleColor.White;
            Console.WriteLine("{0:T} - {1}: \"{2}\" - max severity: {3}", DateTime.Now, messageType, e.Message, maxSeverity);
        }
    }
}

运行 我得到了这个输出,请注意 RAISERROR NOWAIT 和真正的错误会刷新所有待处理的信息消息。

02:38:03 - Execute query...
02:38:07 - Info: "This is the 1st PRINT message." - max severity: 0
02:38:07 - StatementCompleted: 1 rows affected
02:38:07 - Info: "This is the 2nd PRINT message." - max severity: 0
02:38:07 - StatementCompleted: 1 rows affected
02:38:07 - Info: "This is the 1st RAISERROR NOWAIT message, severity 1." - max severity: 1
02:38:10 - StatementCompleted: 1 rows affected
02:38:10 - Info: "This is the 3rd PRINT message." - max severity: 0
02:38:10 - Info: "This is the 2nd RAISERROR NOWAIT message, severity 10." - max severity: 0
02:38:11 - Error: "This is the 3rd RAISERROR NOWAIT message, severity 11." - max severity: 11
02:38:18 - Info: "This is the 4th PRINT message." - max severity: 0
02:38:18 - Error: "Divide by zero error encountered." - max severity: 16
02:38:18 - Info: "This is the 5th PRINT message." - max severity: 0
02:38:18 - StatementCompleted: 1 rows affected
02:38:18 - Error: "Database 'fakedb' does not exist. Make sure that the name is entered correctly." - max severity: 16
02:38:18 - Error: "BACKUP LOG is terminating abnormally." - max severity: 16
02:38:18 - Info: "This is the 6th PRINT message." - max severity: 0
02:38:18 - Info: "This is the final RAISERROR NOWAIT message, severity 10." - max severity: 0
02:38:20 - Info: "This is the final PRINT message." - max severity: 0
02:38:20 - Query finished.
Press Enter to Exit.