是否有相当于
PRINT 'hello world'
哪个可以从 CLR (C#) 代码中调用?
我正在尝试在我的函数中输出一些调试信息。我无法运行 VS 调试器,因为这是远程服务器。
谢谢!
是否有相当于
PRINT 'hello world'
哪个可以从 CLR (C#) 代码中调用?
我正在尝试在我的函数中输出一些调试信息。我无法运行 VS 调试器,因为这是远程服务器。
谢谢!
答案是你不能做相当于
PRINT 'Hello World'
从一个[SqlFunction()]
. 但是,您可以通过[SqlProcedure()]
using
SqlContext.Pipe.Send("hello world")
这与 T-SQL 一致,如果将 PRINT 粘贴在函数中,则会收到错误“在函数中无效使用副作用运算符 'PRINT'”。但是,如果您从存储过程中执行此操作,则不会。
对于解决方法,我建议:
List<string> messages
,并编写另一个返回 内容的表值函数messages
。当然,访问messages
需要同步,因为可能有多个线程同时尝试访问它。[SqlProcedure()]
你应该能够做到:
SqlContext.Pipe.Send("hello world");
如果您在 CLR UDF 中运行它,SqlContext.Pipe
将始终null
如您所见。没有有效的SqlPipe
我不相信你可以做你想做的事。
如果这纯粹是出于调试目的,您始终可以在托管代码中打开一个文件并在那里写入您的输出。但是,这要求您的程序集具有EXTERNAL_ACCESS
权限,而这反过来又要求将数据库标记为可信任的。不一定是我会做或推荐的事情。
啊,我明白了... Jsut 澄清一下:如果您有SqlFunction则 SqlContext.Pipe 不可用,但是在SqlProcedure中它是可用的,您可以使用 Send() 来编写消息。
除了异常消息之外,我还没有找到从 SqlFunction 输出信息的方法。
SQLCLR 函数——标量用户定义函数 (UDF)、表值函数 (TVF)、用户定义聚合 (UDA) 和用户定义类型 (UDT) 中的方法——当使用上下文连接时(即 ConnectionString = "Context Connection = true;"
),受大多数与 T-SQL 函数相同的限制的约束,包括不能PRINT
或RAISERROR('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 本质上都是临时/动态的)。
- 从临时表中选择。
您可以尝试将这些信息通过“xp_logevent”存储过程。您可以将调试信息设置为不同级别的“信息”、“警告”或“错误”。我还尝试将这些调试/错误信息放入事件日志中,但这需要在安全方面进行一些配置,我怀疑我不能在生产中使用它。