如何从 CLR 存储过程调用的 TSQL 中捕获 PRINT 语句

msg*_*sme 3 sql-server sql-clr

我有一个 CLR 存储过程(除其他外)调用 TSQL 存储过程。TSQL 存储过程运行一些动态 SQL,并在运行前打印 SQL 以进行调试。PRINT语句中没有任何内容显示在客户端中。当我们需要排除故障时,有什么好方法可以让我们看到这些命令?

示例代码:

dll中的C#代码

public static void Print_CLR()
{
    using (SqlConnection conn = new SqlConnection("context connection=true"))   
    {
            conn.Open();
            using (SqlCommand c = new SqlCommand("exec dbo.Print_TSQL", conn))
            {   
            c.ExecuteNonQuery();    
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

CLR 调用的 Proc

public static void Print_CLR()
{
    using (SqlConnection conn = new SqlConnection("context connection=true"))   
    {
            conn.Open();
            using (SqlCommand c = new SqlCommand("exec dbo.Print_TSQL", conn))
            {   
            c.ExecuteNonQuery();    
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Sol*_*zky 6

为了在 .NET 中捕获消息(来自PRINTRAISERROR('', 1, 10)喜欢它)(无论是调用存储过程还是临时 SQL),您需要设置一个将被SqlConnection.InfoMessage事件调用的方法。

基本实现如下:

[SqlProcedure()]
public static void Print_CLR()
{
    using (SqlConnection conn = new SqlConnection("context connection=true"))   
    {
        conn.InfoMessage += new SqlInfoMessageEventHandler(CaptureMessage);
        conn.Open();

        using (SqlCommand c = new SqlCommand("exec dbo.Print_TSQL", conn))
        {   
            c.ExecuteNonQuery();    
        }
    }
}

private static void CaptureMessage(object sender, SqlInfoMessageEventArgs args)
{
  // do something with args.Message

  return;
}
Run Code Online (Sandbox Code Playgroud)

此时,您仍然需要对 Message 做一些事情。虽然有几个选项,但其中一些选项由于环境原因具有安全隐患。与在用于 Windows 应用程序、控制台应用程序和 ASP.NET 应用程序的操作系统上运行的主 CLR 相比,SQL Server 的 CLR 主机受到高度限制。

如果您使用static如上所示的方法方法,那么您对 ​​Message 执行的操作将影响PERMISSION_SET包含此代码的程序集的值:

  • 如果您只是想将消息打印回用户,那么您可以执行以下操作,这些操作可以在程序集中完成SAFE

    SqlContext.Pipe.Send(args.Message);
    
    Run Code Online (Sandbox Code Playgroud)
  • 如果要将消息写入文件,则可以执行以下操作,这些操作可以在程序集中完成EXTERNAL_ACCESS

    File.AppendAllText(FILE_PATH_CONSTANT, args.Message);
    
    Run Code Online (Sandbox Code Playgroud)

    这可能会导致额外的延迟并且不值得。但另一方面,如果该过程在中间失败,则写入文件的任何内容仍然存在,因此这种方法肯定有一些好处。

  • 但是如果你想将消息存储在一个变量中以便以后做一些事情,那么你将需要一个静态类变量,因为捕获消息的方法是static. 例如,您可以声明private static m_Messages StringBuilder = new StringBuiler("");. 然后,在您的CaptureMessage方法中,您可以执行以下操作:

    m_Messages.AppendLine(args.Message);
    
    Run Code Online (Sandbox Code Playgroud)

    这看起来很简单。并且对于 SQLCLR 之外的大多数环境来说都是如此。但是,在 SQLCLR 中使用静态变量要求程序集具有PERMISSION_SETof UNSAFE,并且最好尽可能避免这种情况。需要将程序集标记为的原因UNSAFE是运行此代码的 AppDomain 在所有会话/SPID 之间共享。因此,静态类变量也在会话之间共享,这可能导致非常奇怪的行为

值得庆幸的是,如果您想将消息收集到实例变量,则不会失去所有希望。为此,您需要使用匿名委托内联定义处理程序。匿名方法与代码的其余部分在同一范围内,因此它可以访问局部变量。这非常方便,因为它允许您轻松地将消息存储在具有PERMISSION_SETof SAFE(非常受欢迎)的程序集中。

[SqlProcedure()]
public static void Print_CLR()
{
    StringBuilder _Messages = new StringBuilder("");

    using (SqlConnection conn = new SqlConnection("context connection=true"))   
    {
        conn.InfoMessage += delegate(object sender, SqlInfoMessageEventArgs args)
        {
            SqlConnection _Connection = (SqlConnection)sender;
            // _Connection.DataSource is the "Server" of the ConnectionString
            // _Connection.Database is the CURRENT database of the Connection

            _Messages.Append(args.Message);

            return;
        };

        conn.Open();

        using (SqlCommand c = new SqlCommand("exec dbo.Print_TSQL", conn))
        {   
            c.ExecuteNonQuery();    
        }
    }
}
Run Code Online (Sandbox Code Playgroud)