Olá pessoal,
Bom dia!

Neste post eu gostaria de demonstrar para vocês, já que estou (e pretendo continuar) postando várias coisas legais sobre o CLR, como enviar avisos (PRINT) e mensagens de erro (RAISEERROR) para o SQL Server quando suas Stored Procedures compiladas no CLR são executadas.

Apesar do post ser pequeno, resolvi criar um post só com isso, porque utilizo muito essa classe nas SP’s que vou publicar aqui futuramente, então entendo que seja mais fácil criar essa referência do que repostar essa classe várias vezes.

Código fonte do arquivo Servidor.cs (pré-requisito)

using System;
using System.Collections.Generic;
using System.Data.SqlClient;

namespace Bibliotecas.Model
{

    public class ServidorAtual
    {

        public string NomeServidor { get; set; }

        public ServidorAtual()
        {

            try
            {

                using (var conn = new SqlConnection(Servidor.Context))
                {

                    conn.Open();

                    using (var cmd = conn.CreateCommand())
                    {
                        cmd.CommandText = "SELECT @@SERVERNAME AS InstanceName";
                        NomeServidor = (string) cmd.ExecuteScalar();
                    }

                    var partes = NomeServidor.Split('\\');

                    if (partes.Length <= 1) return;
                    if (string.Equals(partes[0], partes[1], StringComparison.CurrentCultureIgnoreCase))
                        NomeServidor = partes[0];
                }

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

    }


    public static class Servidor
    {

        public static string Ds_Usuario => "Usuario";
        public static string Ds_Senha => "Senha";

        public static string PRODUCAO => "data source=PRODUCAO;initial catalog=CLR;persist security info=False;Enlist=False;packet size=4096;user id='" + Ds_Usuario + "';password='" + Ds_Senha + "'";
        public static string Context => "context connection=true";
        public static string Localhost => "data source=LOCALHOST;initial catalog=CLR;persist security info=False;Enlist=False;packet size=4096;user id='" + Ds_Usuario + "';password='" + Ds_Senha + "'";

        public static string getLocalhost()
        {

            var servidorAtual = new ServidorAtual().NomeServidor;
            return "data source=" + servidorAtual + ";initial catalog=CLR;persist security info=False;Enlist=False;packet size=4096;user id='" + Ds_Usuario + "';password='" + Ds_Senha + "'";

        }

        public static List<string> Servidores
        {
            get
            {
                var servidores = new List<string>
                {
                    PRODUCAO,
                    Localhost
                };

                return servidores;

            }
        }

    }

}

Código-fonte da classe Retorno:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using Microsoft.SqlServer.Server;

namespace Bibliotecas.Model
{

    public static class Retorno
    {

        public static void Erro(string erro)
        {

            /*

            IF (OBJECT_ID('CLR.dbo.Log_Erro') IS NOT NULL) DROP TABLE CLR.dbo.Log_Erro
            CREATE TABLE CLR.dbo.Log_Erro (
	            Id_Erro INT IDENTITY(1,1),
	            Dt_Erro DATETIME DEFAULT GETDATE(),
	            Nm_Objeto VARCHAR(100),
	            Ds_Erro VARCHAR(MAX),
	            CONSTRAINT [PK_Log_Erro] PRIMARY KEY CLUSTERED (Id_Erro)
            )

            */

            using (var conexao = new SqlConnection(Servidor.getLocalhost()))
            {

                var comando = new SqlCommand("INSERT INTO dbo.Log_Erro (Nm_Objeto, Ds_Erro) VALUES (@Nm_Objeto, @Ds_Erro)", conexao);

                var stackTrace = new StackTrace();
                var objeto = stackTrace.GetFrame(1).GetMethod().Name;

                comando.Parameters.Add(new SqlParameter("@Nm_Objeto", SqlDbType.VarChar, 100)).Value = objeto;
                comando.Parameters.Add(new SqlParameter("@Ds_Erro", SqlDbType.VarChar, 8000)).Value = erro;
                conexao.Open();

                comando.ExecuteNonQuery();
            }
            

            throw new ApplicationException(erro);
        }


        public static void Mensagem(string mensagem)
        {

            using (var conexao = new SqlConnection(Servidor.Context))
            {

                var Comando = new SqlCommand("IF ( (512 & @@OPTIONS) = 512 ) select 1 else select 0", conexao);
                conexao.Open();

                if ((int) Comando.ExecuteScalar() != 0) return;

                var retorno = SqlContext.Pipe;
                retorno?.Send(mensagem.Length > 4000 ? mensagem.Substring(0, 4000) : mensagem);
            }

        }

        public static void RetornaReader(SqlDataReader dataReader)
        {
            var retorno = SqlContext.Pipe;
            retorno?.Send(dataReader);
        }
    }

    public class Ret : Exception
    {
        public Ret(string str) : base(str)
        {      
        }
    }
}

Uma vez que essa classe criada no seu projeto CLR, basta importá-la na sua Stored Procedure e começar a enviar avisos e mensagens de erro, como vou demonstrar abaixo:
Simulação de um erro num método do CLR:

using Bibliotecas.Model;
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void stpTeste(SqlString Ds_String)
    {

        try
        {

            if (Ds_String.Value == "ERRO")
            {
                // Vou forçar um erro ao tentar inserir dados em uma tabela que não existe, causando uma Exception
                using (var Conexao = new SqlConnection(Servidor.Localhost))
                {

                    var Comando = new SqlCommand("INSERT INTO dbo.Erro (Nm_Objeto, Ds_Erro) VALUES (@Nm_Objeto, @Ds_Erro)", Conexao);

                    Comando.Parameters.Add(new SqlParameter("@Nm_Objeto", SqlDbType.VarChar, 100)).Value = "Vai dar erro";
                    Comando.Parameters.Add(new SqlParameter("@Ds_Erro", SqlDbType.VarChar, 8000)).Value = "Descrição do Erro";
                    Conexao.Open();

                    Comando.ExecuteNonQuery();
                }
            }

            Retorno.Mensagem("Alerta enviado para o banco (PRINT)");

        }
        catch(Exception e)
        {
            Retorno.Erro("Mensagem de erro (RAISEERROR) gravada. Descrição do erro: " + e.Message);
        }

    }

}

Exemplo de utilização:

SQL Server - sql server clr c# csharp enviar avisos mensagens de erro warnings send text print error messages 2
SQL Server - sql server clr c# csharp enviar avisos mensagens de erro warnings send text print error messages 2

Consultando o histórico de erros:
Como vocês devem ter reparado, no método de erro eu coloquei uma instrução SQL para gravar o histórico de quando esse método é chamado, fazendo com que se tenha um log de erros do CLR, facilitando a localização de possíveis problemas nas suas procedures CLR.

SQL Server - sql server clr c# csharp mensagens de erro print error messages
SQL Server - sql server clr c# csharp mensagens de erro print error messages

É isso aí, pessoal!
Qualquer dúvida, deixem aqui nos comentários.

Abraço.

sql server clr c# csharp enviar avisos mensagens de erro warnings send text print error messages

sql server clr c# csharp enviar avisos mensagens de erro warnings send text print error messages