如何将参数传递给外部 (SQLCLR) SQL 服务器触发器
How to pass arguments to an external (SQLCLR) SQL Server trigger
我创建了一个调用程序集的触发器,如下所示:
CREATE TRIGGER Testrigger ON STATION
FOR INSERT
AS EXTERNAL NAME assemblytest.[WriteTimeInfile.Program].Testrigger
该程序集中执行如下操作的 .NET 代码:
namespace WriteTimeInfile
{
public class Program
{
[SqlTrigger(Name = @"Testrigger", Target = "[dbo].[STATION]", Event = "FOR INSERT, UPDATE, DELETE")]
public static void Testrigger()
{
File.AppendAllText(@"C:\Users\Vivien\date.txt",
DateTime.Now.ToString() + Environment.NewLine);
}
}
}
我希望能够像这样传递创建的行或更新的行作为参数:
CREATE TRIGGER Testrigger ON STATION
AFTER INSERT
AS
EXTERNAL NAME assemblytest.[WriteTimeInfile.Program].Testrigger (STATION.ID)
我在 Whosebug 上发现了一个 7 岁的 topic,告诉我无法将参数传递给 CLR 程序集。
我在问现在是否可以在最近的 SQL 服务器版本中实现。
你知道有没有办法,如果有,请问怎么做?
不,您不能直接将参数传递给 SQLCLR 触发器。但是,您可以通过几种方式间接传递值(与常规 T-SQL 触发器相同):
- 本地临时 table
- SET CONTEXT_INFO / CONTEXT_INFO
- 在 SQL 服务器 2016 或更新版本上:sp_set_session_context / SESSION_CONTEXT
在所有情况下,您都可以通过执行带有输出 SqlParameter
的 SqlCommand
将值提取到 .NET 代码中来获取值。 (有关用法,请参阅末尾的注释)。
但是,如果您只想要 inserted
and/or deleted
table 的值,那么这些将不是参数。只有 SELECT
那些使用 SqlCommand
的连接字符串使用 Context Connection = true
和 SqlDataReader
。您可以在 CLR Triggers, in the Sample CLR Trigger 部分的 MSDN 页面中查看此示例。
关于将值传递给不属于 DML 操作的触发器的注意事项:
虽然这不是非常常见的做法,但肯定有有效的用例将一条信息从主上下文传递到链中的一个或多个触发器事件。我遇到的两种最常见的情况是:1) 将基于应用程序的登录名或用户 ID(不是 SQL 服务器的一部分)传递给删除行的审计触发器(因为该信息无法添加到 ModifiedBy DELETE
操作中的列),以及 2) 根据条件暂时禁用触发器。是的,这是可能的,而且确实有效。请参阅我在 DBA.StackExchange 上的以下回答:
INSERTED 和 DELETED 伪表始终可用于在 SQLCLR 触发器内直接查询,如文档 this 9+ years old version 中所示。您可以随时查询它们,例如:
using (SqlConnection conn = new SqlConnection("context connection=true"))
{
conn.Open();
SqlCommand sqlComm = new SqlCommand();
SqlPipe sqlP = SqlContext.Pipe;
sqlComm.Connection = conn;
sqlComm.CommandText = "SELECT UserName from INSERTED";
userName.Value = sqlComm.ExecuteScalar().ToString();
if (IsEMailAddress(userName.Value.ToString()))
{
sqlComm.Parameters.Add(userName);
sqlComm.CommandText = "INSERT UsersAudit(UserName) VALUES(@username)";
sqlP.Send(sqlComm.CommandText);
sqlP.ExecuteAndSend(sqlComm);
}
}
最新样本相同
您不需要将它们作为参数传递,就像您不需要将它们作为参数传递给普通触发器一样。这些表始终可供查询。
我怀疑这些表没有作为上下文对象上的集合公开,因为这需要从 SQL 服务器的缓冲区中复制它们(浪费 CPU 和内存)。另一个原因是无法有效查询集合。您要么必须使用 LINQ(使用更多 CPU),要么只是迭代整个内容(内存和 CPU 浪费)。内存浪费会是更大的问题,因为这些内存可以用来缓冲更多的数据和索引,从而加快访问速度。
我怀疑您链接到的问题想问同样的事情,但 OP 假设必须将伪表作为参数传递。所以他问的是参数而不是实际问题。
我创建了一个调用程序集的触发器,如下所示:
CREATE TRIGGER Testrigger ON STATION
FOR INSERT
AS EXTERNAL NAME assemblytest.[WriteTimeInfile.Program].Testrigger
该程序集中执行如下操作的 .NET 代码:
namespace WriteTimeInfile
{
public class Program
{
[SqlTrigger(Name = @"Testrigger", Target = "[dbo].[STATION]", Event = "FOR INSERT, UPDATE, DELETE")]
public static void Testrigger()
{
File.AppendAllText(@"C:\Users\Vivien\date.txt",
DateTime.Now.ToString() + Environment.NewLine);
}
}
}
我希望能够像这样传递创建的行或更新的行作为参数:
CREATE TRIGGER Testrigger ON STATION
AFTER INSERT
AS
EXTERNAL NAME assemblytest.[WriteTimeInfile.Program].Testrigger (STATION.ID)
我在 Whosebug 上发现了一个 7 岁的 topic,告诉我无法将参数传递给 CLR 程序集。
我在问现在是否可以在最近的 SQL 服务器版本中实现。
你知道有没有办法,如果有,请问怎么做?
不,您不能直接将参数传递给 SQLCLR 触发器。但是,您可以通过几种方式间接传递值(与常规 T-SQL 触发器相同):
- 本地临时 table
- SET CONTEXT_INFO / CONTEXT_INFO
- 在 SQL 服务器 2016 或更新版本上:sp_set_session_context / SESSION_CONTEXT
在所有情况下,您都可以通过执行带有输出 SqlParameter
的 SqlCommand
将值提取到 .NET 代码中来获取值。 (有关用法,请参阅末尾的注释)。
但是,如果您只想要 inserted
and/or deleted
table 的值,那么这些将不是参数。只有 SELECT
那些使用 SqlCommand
的连接字符串使用 Context Connection = true
和 SqlDataReader
。您可以在 CLR Triggers, in the Sample CLR Trigger 部分的 MSDN 页面中查看此示例。
关于将值传递给不属于 DML 操作的触发器的注意事项:
虽然这不是非常常见的做法,但肯定有有效的用例将一条信息从主上下文传递到链中的一个或多个触发器事件。我遇到的两种最常见的情况是:1) 将基于应用程序的登录名或用户 ID(不是 SQL 服务器的一部分)传递给删除行的审计触发器(因为该信息无法添加到 ModifiedBy DELETE
操作中的列),以及 2) 根据条件暂时禁用触发器。是的,这是可能的,而且确实有效。请参阅我在 DBA.StackExchange 上的以下回答:
INSERTED 和 DELETED 伪表始终可用于在 SQLCLR 触发器内直接查询,如文档 this 9+ years old version 中所示。您可以随时查询它们,例如:
using (SqlConnection conn = new SqlConnection("context connection=true"))
{
conn.Open();
SqlCommand sqlComm = new SqlCommand();
SqlPipe sqlP = SqlContext.Pipe;
sqlComm.Connection = conn;
sqlComm.CommandText = "SELECT UserName from INSERTED";
userName.Value = sqlComm.ExecuteScalar().ToString();
if (IsEMailAddress(userName.Value.ToString()))
{
sqlComm.Parameters.Add(userName);
sqlComm.CommandText = "INSERT UsersAudit(UserName) VALUES(@username)";
sqlP.Send(sqlComm.CommandText);
sqlP.ExecuteAndSend(sqlComm);
}
}
最新样本相同
您不需要将它们作为参数传递,就像您不需要将它们作为参数传递给普通触发器一样。这些表始终可供查询。
我怀疑这些表没有作为上下文对象上的集合公开,因为这需要从 SQL 服务器的缓冲区中复制它们(浪费 CPU 和内存)。另一个原因是无法有效查询集合。您要么必须使用 LINQ(使用更多 CPU),要么只是迭代整个内容(内存和 CPU 浪费)。内存浪费会是更大的问题,因为这些内存可以用来缓冲更多的数据和索引,从而加快访问速度。
我怀疑您链接到的问题想问同样的事情,但 OP 假设必须将伪表作为参数传递。所以他问的是参数而不是实际问题。