I have a CLR stored procedure that (among other things) calls a TSQL stored procedure. The TSQL stored procedure runs some dynamic SQL, and it prints the SQL before it runs for debugging purposes. Nothing from the PRINT
statements shows up in the client. What is a good way to allow us to see these commands when we need to troubleshoot?
Sample code:
C# code in dll
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();
}
}
}
Proc called by CLR
CREATE PROC DBO.Print_TSQL AS
PRINT 'WTF'
GO
--Exposing proc to SQL
CREATE PROC dbo.Print_CLR AS
EXTERNAL NAME
[CLRUtility].[CLRUtility.CLRUtility].Print_CLR
GO
--Executing, nothing is PRINTed for the client
EXEC dbo.Print_CLR
Best Answer
In order to capture messages (either from
PRINT
orRAISERROR('', 1, 10)
or like it) in .NET (regardless of calling a Stored Procedure or ad hoc SQL), you need to set up a method that will get called by the SqlConnection.InfoMessage event.The basic implementation is as follows:
At this point, you still need to do something with the Message. While there are a few options, some of them have security implications due to the environment. SQL Server's CLR host is highly constrained compared to the main CLR running on the OS that is used for Windows Apps, Console Apps, and ASP.NET apps.
If you use the
static
method approach as shown above, then what you do with the Message will impact thePERMISSION_SET
value for the assembly containing this code:If you simply want to print the message back to the user, then you can do something like the following which can be done in a
SAFE
assembly:If you want to write the messages to a file, then you can do something like the following which can be done in an
EXTERNAL_ACCESS
assembly:This might cause extra latency and not be worth it. But on the other hand, if the process fails in the middle, whatever was written to the file would still be there, so there is definitely some benefit to this approach.
But if you want to store the messages in a variable to do something with later, then you will need a static class variable since the method to capture the message is
static
. For example, you can declareprivate static m_Messages StringBuilder = new StringBuiler("");
. Then, in yourCaptureMessage
method you can do the following:This seems simple enough. And for most any environment outside of SQLCLR it is. However, using a static variable in SQLCLR requires that the assembly have a
PERMISSION_SET
ofUNSAFE
, and it is best to avoid that if at all possible. And the reason that the assembly is required to be marked asUNSAFE
is that the AppDomain where this code is running is shared across all Sessions / SPIDs. Hence, a static class variable is also shared across Sessions and that can lead to very odd behaviour.Thankfully, all hope is not lost if you want to collect messages to an instance variable. To do this, you need to define the handler inline using an anonymous delegate. The anonymous method is within the same scope as the rest of your code so it has access to local variables. This is quite handy as it allows you to easily store the messages in an assembly that has a
PERMISSION_SET
ofSAFE
(which is highly preferred).