【问题标题】:How to pass arguments to an external (SQLCLR) SQL Server trigger如何将参数传递给外部 (SQLCLR) SQL Server 触发器
【发布时间】:2017-01-03 09:53:30
【问题描述】:

我创建了一个触发器,它像这样调用程序集:

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)

我在 StackOverflow 上发现了一个 7 岁的 topic,它告诉我无法将参数传递给 CLR 程序集。
我在问最近的 SQL Server 版本现在是否可以实现。

你知道有没有办法,如果有,请问怎么做?

【问题讨论】:

  • 您始终可以从 SQLCLR 触发器 as shown in this old version of the documendation 中访问 INSERTED 和 DELETED 表。您不需要将任何参数传递给触发器,就像您不需要将任何参数传递给普通触发器一样
  • 为什么你想将参数传递给触发器?什么样的参数?为什么不使用存储过程呢?如果您想通过更改检测实现某种 ETL 功能,您可以使用更改跟踪(不是 CDC)来查找键并更改已修改行的类型并进行处理

标签: .net sql-server tsql sqlclr database-trigger


【解决方案1】:

不,您不能直接将参数传递给 SQLCLR 触发器。但是,您可以通过几种方式间接传递值(与常规 T-SQL 触发器相同):

  1. 本地临时表
  2. SET CONTEXT_INFO / CONTEXT_INFO
  3. 在 SQL Server 2016 或更高版本上:sp_set_session_context / SESSION_CONTEXT

在所有情况下,您都可以通过执行带有输出 SqlParameterSqlCommand 来获取值,以将值拉入 .NET 代码。 (使用说明见文末说明)。

但是,如果您只想要 inserted 和/或 deleted 表的值,那么它们就不是参数或参数。只有SELECT 那些使用SqlCommand 的人使用Context Connection = true 作为连接字符串,以及SqlDataReader。您可以在 CLR Triggers 的 MSDN 页面中的 Sample CLR Trigger 部分查看此示例。


关于将值传递给不属于 DML 操作的触发器的注意事项:

虽然这不是非常常见的做法,但肯定有有效的用例可以将一条信息从主上下文传递到事件链中的一个或多个触发器。我遇到的两个最常见的情况是:1)将基于应用程序的登录名或用户 ID(不是 SQL Server 的一部分)传递给删除行的审核触发器(因为该信息无法添加到 @ 中的 ModifiedBy 列987654337@ 操作),以及 2) 根据条件临时禁用触发器。是的,这是可能的,而且确实有效。请在 DBA.StackExchange 上查看我的以下答案:

【讨论】:

  • 尝试像使用存储过程一样使用触发器可能意味着它们一开始就不应该是触发器
  • @PanagiotisKanavos 如果这是一个尝试使用触发器的情况,就好像它们是存储过程一样,那么我会同意你的看法:-)。但肯定有使用触发器的情况,因为它们应该被使用,但有时仍然会遇到这种需求。
  • @srutzky 谢谢你,你正在成为我的官方问题解决者
【解决方案2】:

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 Server 的缓冲区中复制它们(浪费 CPU 和内存)。另一个原因是无法有效查询集合。您要么必须使用 LINQ(使用更多 CPU),要么只需遍历整个内容(内存和 CPU 浪费)。内存浪费将是更大的问题,因为该内存可用于缓冲更多数据和索引,从而加快访问速度。

我怀疑您链接到的问题想问同样的问题,但 OP 认为必须将伪表作为参数传递。所以他问的是参数而不是实际问题。

【讨论】:

  • 谢谢,很高兴知道,但我希望能够从更新的行或任何参数传递参数,
  • 无论如何你都不能用任何触发器来做到这一点,因为它没有意义。触发器由服务器执行,因此您无法操作它们之外的值。至于 rows(复数),这些已经可用。 复数是重要的部分。它们可以是该 INSERTED 或 DELETED 表中的一百万行或一百万行。语法发生了变化,但访问表的方式保持不变
  • @VivienPipo 和 Panagiotis:虽然这不是非常常见的做法,但肯定有有效的用例将一条信息从主上下文传递到一个或多个事件链中的触发器。我遇到的两个最常见的情况是:1)为删除行的人传递基于应用程序的登录名或用户ID(不是SQL Server的一部分)(因为该信息无法添加到DELETE中的ModifiedBy列中操作),以及 2)根据条件临时禁用触发器。是的,正如我在回答中所展示的那样,这是可能的并且确实有效:-)。
  • @srutzky 更好的是,完全避免此类触发器并在适当的情况下使用存储过程。在 INSERT/UPDATE 语句本身中使用该登录 ID。
  • @PanagiotisKanavos 请重新阅读我写的内容。在该示例中,它是一个审计触发器(不是可以可靠移动到存储过程的东西),它特别关注DELETE 语句,而不是INSERT 或UPDATE。您无法将值添加到行,因为它们正在被删除。此外,有时您的 ETL 触发器不是长期的,但迁移需要,您可能需要出于各种原因暂时禁用它们。我曾在遇到这两种情况的地方工作过,从关于 S.O. 的问题中可以清楚地看出。和 DBA.SE,其他人也会遇到这些情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-29
  • 2022-11-11
  • 1970-01-01
  • 1970-01-01
  • 2011-03-01
  • 2013-07-17
  • 1970-01-01
相关资源
最近更新 更多