35

是否有相当于

PRINT 'hello world'

哪个可以从 CLR (C#) 代码中调用?

我正在尝试在我的函数中输出一些调试信息。我无法运行 VS 调试器,因为这是远程服务器。

谢谢!

4

5 回答 5

34

答案是你不能做相当于

PRINT 'Hello World'

从一个[SqlFunction()]. 但是,您可以通过[SqlProcedure()]using

SqlContext.Pipe.Send("hello world")

这与 T-SQL 一致,如果将 PRINT 粘贴在函数中,则会收到错误“在函数中无效使用副作用运算符 'PRINT'”。但是,如果您从存储过程中执行此操作,则不会。

对于解决方法,我建议:

  1. 从您的代码中使用Debug.Print,并将调试器附加到 SQL Server(我知道这对您不起作用,正如您所解释的那样)。
  2. 将消息保存在全局变量中,例如List<string> messages,并编写另一个返回 内容的表值函数messages。当然,访问messages需要同步,因为可能有多个线程同时尝试访问它。
  3. 将您的代码移动到[SqlProcedure()]
  4. 添加一个参数'debug',当=1时,函数将返回消息作为返回表的一部分(假设有一列带有文本..)
于 2009-07-21T04:39:11.853 回答
10

你应该能够做到:

SqlContext.Pipe.Send("hello world");

如果您在 CLR UDF 中运行它,SqlContext.Pipe将始终null如您所见。没有有效的SqlPipe我不相信你可以做你想做的事。

如果这纯粹是出于调试目的,您始终可以在托管代码中打开一个文件并在那里写入您的输出。但是,这要求您的程序集具有EXTERNAL_ACCESS权限,而这反过来又要求将数据库标记为可信任的。不一定是我会做或推荐的事情。

于 2009-01-29T19:22:10.703 回答
2

啊,我明白了... Jsut 澄清一下:如果您有SqlFunction则 SqlContext.Pipe 不可用,但是在SqlProcedure中它是可用的,您可以使用 Send() 来编写消息。

除了异常消息之外,我还没有找到从 SqlFunction 输出信息的方法。

于 2009-01-30T19:47:54.710 回答
2

SQLCLR 函数——标量用户定义函数 (UDF)、表值函数 (TVF)、用户定义聚合 (UDA) 和用户定义类型 (UDT) 中的方法——当使用上下文连接时(即 ConnectionString = "Context Connection = true;"),受大多数与 T-SQL 函数相同的限制的约束,包括不能PRINTRAISERROR('message', 10, 1). 但是,您确实有一些选择。

在我们讨论这些选项之前,应该声明:

  • 您无需切换到使用存储过程。如果想要一个功能,那就坚持一个功能。

  • 添加“调试”参数并为此更改输出似乎有点极端,因为 UDF(T-SQL 和 SQLCLR)函数不允许重载。因此调试参数将始终在签名中。如果你想触发调试,只需创建一个名为#debug(或类似的东西)的临时表并通过SELECT OBJECT_ID(N'tempdb..#debug');使用"Context Connection = true;"ConnectionString 进行测试(这很快,可以在安全模式下完成,并且是同一会话的一部分,因此它可以看到临时表)。从中得到结果if (SqlCommand.ExecuteScalar() == DBNull.Value)

  • 请不要使用全局(即静态)变量。这比必要的复杂得多,并且(通常)要求将 Assembly 设置为UNSAFE,如果可能的话,应该避免这种情况。

因此,如果您至少可以将程序集设置为EXTERNAL_ACCESS,那么您有几个选择。并且这样做不需要将数据库设置为TRUSTWORTHY ON. 这是一个非常普遍(也是不幸)的误解。您只需要对程序集进行签名(无论如何这是一个很好的做法),然后[master]从 DLL 创建一个非对称密钥 (in ),然后基于该非对称密钥创建一个 Login ,最后授予 Login EXTERNAL ACCESS ASSEMBLY。之后(一次),您可以执行以下任何操作:

  • 使用File.AppendAllText (String path, String contents)将消息写入文件。当然,如果您无权访问文件系统,那么这将无济于事。如果网络上有可以访问的共享驱动器,那么只要 SQL Server 服务的服务帐户有权在该共享上创建和写入文件,那么这将起作用。如果存在服务帐户没有权限但您的域/Active Directory 帐户有权限的共享,那么您可以将该File.AppendAllText调用包装在:

    using (WindowsImpersonationContext _Impersonate = 
                          SqlContext.WindowsIdentity.Impersonate())
    {
       File.AppendAllText("path.txt", _DebugMessage);
        _Impersonate.Undo();
    }
    
  • 连接到 SQL Server 并将消息写入表。它可以是当前/本地 SQL Server 或任何其他 SQL Server。您可以在其中创建一个表,[tempdb]以便在下次重新启动 SQL Server 时自动清理它,否则会一直持续到那个时间,或者直到您删除它。进行常规/外部连接允许您执行 DML 语句。然后,您可以在运行该函数时从表中进行选择。

  • 将消息写入环境变量。自 Vista / Server 2008 以来,环境变量的大小并没有完全受到限制,尽管它们并不真正处理换行符。但在 .NET 代码中设置的任何变量也将继续存在,直到重新启动 SQL Server 服务。您可以通过读取当前值并将新消息连接到末尾来附加消息。就像是:

    {
      string _Current = System.Environment.GetEnvironmentVariable(_VariableName,
                                      EnvironmentVariableTarget.Process);
    
      System.Environment.SetEnvironmentVariable(
          _VariableName,
          _Current + _DebugMessage,
          EnvironmentVariableTarget.Process);
    }
    

应该注意的是,在这 3 种情况下,都假设测试是以单线程方式进行的。如果该函数将同时从多个会话运行,那么您需要一种分离消息的方法。在这种情况下,您可以获得当前的“transaction_id”(所有查询,即使没有 aBEGIN TRAN也是一个事务!)对于任何特定的执行应该是一致的(在同一个函数中的多次使用以及如果每个函数都被调用)跨多行的行)。如果使用文件或环境变量方法,您可以将此值用作消息的前缀,如果存储到表中,则可以用作单独的字段。您可以通过执行以下操作获得交易:

int _TransactionID;

using (SqlConnection _Connection = new SqlConnection("Context Connection = true;"))
{
    using (SqlCommand _Command = _Connection.CreateCommand())
    {
        _Command.CommandText = @"
SELECT transaction_id
FROM sys.dm_exec_requests
WHERE session_id = @@SPID;
";

        _Connection.Open();
        _TransactionID = (int)_Command.ExecuteScalar();
    }
}

有关 T-SQL 和 SQLCLR 函数的附加信息

下面的列表最初取自 MSDN 页面的Create User-defined Functions (Database Engine),然后由我编辑,如上所述,以反映 T-SQL 函数和 SQLCLR 函数之间的差异:

  • 用户定义的函数不能用于执行修改数据库状态的操作。
  • 用户定义的函数不能包含以表为目标的 OUTPUT INTO 子句。
  • 用户定义的函数不能返回多个结果集。如果需要返回多个结果集,请使用存储过程。
  • 错误处理受限于用户定义的函数。UDF 不支持 TRY...CATCH、@@ERROR 或 RAISERROR。[注意:这是在 T-SQL 方面,无论是本机的还是从 SQLCLR 函数提交的。您可以在 .NET 代码中使用 try / catch / finally / throw。]
  • 用户定义的函数中不允许使用 SET 语句。
  • 不允许使用 FOR XML 子句
  • 用户自定义函数可以嵌套;...嵌套级别在被调用函数开始执行时递增,并在被调用函数完成执行时递减。用户自定义函数最多可嵌套 32 层。
  • 以下 Service Broker 语句不能包含在 Transact-SQL 用户定义函数的定义中:
    • 开始对话
    • 结束对话
    • 获取对话组
    • 移动对话
    • 收到
    • 发送

以下与 T-SQL 函数和 SQLCLR 函数有关:

  • 无法使用打印
  • 不能调用 NEWID() [好吧,除非你SELECT NEWID()从一个视图中。但在 .NET 代码中,您可以使用Guid.NewGuid(). ]

以下仅适用于 T-SQL 函数:

  • 用户定义函数不能调用存储过程,但可以调用扩展存储过程。
  • 用户定义的函数不能使用动态 SQL 或临时表。允许使用表变量。

相反,SQLCLR 函数可以:

  • 执行存储过程,只要它们是只读的。
  • 使用动态 SQL(从 SQLCLR 提交的所有 SQL 本质上都是临时/动态的)。
  • 从临时表中选择。
于 2015-08-26T08:42:59.677 回答
1

您可以尝试将这些信息通过“xp_logevent”存储过程。您可以将调试信息设置为不同级别的“信息”、“警告”或“错误”。我还尝试将这些调试/错误信息放入事件日志中,但这需要在安全方面进行一些配置,我怀疑我不能在生产中使用它。

于 2013-09-12T14:52:39.950 回答