在 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.
基本就是这样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.