【问题标题】:How to know TriggerContext's currently updated table?如何知道 TriggerContext 当前更新的表?
【发布时间】:2015-09-04 18:48:40
【问题描述】:

我有一个数据库,其中的数据必须有条件地复制到同一服务器上的不同数量的数据库中。接收表将具有相同的名称,但只有键列。

我想将数据库浏览逻辑放在 SQLCLR 中,并在源表中的数据更新后立即做出反应。因此,我希望重用相同的复制方法,根据当前更新的表复制键值。

internal static void ReplicateToInstances()
{
    if (!IsTableWriteTriggerContextAvailable()) throw new InvalidOperationException();

    string currentlyUpdatedTable; // = SqlContext.TriggerContext.??????        

    // rest of the code, irrelevant to the current issue.
}

有没有办法知道当前TriggerContext 实例在哪个表上?

【问题讨论】:

    标签: c# .net sql-server sqlclr


    【解决方案1】:

    应该是显而易见的,但不幸的是它不是。 T-SQL 触发器可以使用@@PROCID,然后在sys.objects 中查找父对象,但对于SQLCLR 则不然。这是一个建议修复此问题的 Microsoft Connection 项:

    A SQLCLR trigger should be given the parent object in the SQLTriggerContext

    这里有一个关于重复问题的解决方法:

    Retrieve the sqlobject that fired the trigger in clr

    该变通办法(即使用CONTEXT_INFO)的问题在于,如果以下任一条件为真,它就不起作用:

    1. CONTEXT_INFO 已被用于其他目的。

    2. 触发器可以更新一个表,该表上还有一个 SQLCLR 触发器,需要知道其父对象的名称。

    但是,使用sp_settriggerorder 过程将顺序设置为First 是正确的。

    我确实对此有一个可能可以工作的修复程序,即使在嵌套触发器场景中,但我只是没有时间测试它(请参阅 UPDATE 部分以下)。不过,这个概念是在INSERT, UPDATE, DELETE 触发器中按照以下方式做一些事情(以及将触发器顺序设置为First):

    CREATE TRIGGER [SchemaName].[tr_SetTriggerInfo]
    ON    [SchemaName].[TableName]
    AFTER INSERT, DELETE, UPDATE
    AS 
    BEGIN
    
      CREATE TABLE #TriggerInfo (TriggerObjectID INT, TriggerName sysname,
                                 ParentObjectID INT, ParentName sysname, TriggerNestLevel INT);
    
      INSERT INTO #TriggerInfo (TriggerObjectID, TriggerName,
                                ParentObjectID, ParentName, TriggerNestLevel)
          SELECT @@PROCID,
                 trggr.[name] AS [TriggerName],
                 trggr.[parent_object_id] AS [ParentObjectID],
                 OBJECT_NAME(trggr.[parent_object_id]) AS [ParentName],
                 TRIGGER_NESTLEVEL(@@PROCID, 'AFTER', 'DML') AS [TriggerNestLevel]
          FROM   sys.objects trggr
          WHERE  trggr.[object_id] = @@PROCID;
    END;
    GO
    
    EXEC sp_SetTriggerOrder @TriggerName = N'[SchemaName].[tr_SetTriggerInfo]',
                            @Order = N'First',
                            @StmtType = N'DELETE';
    
    EXEC sp_SetTriggerOrder @TriggerName = N'[SchemaName].[tr_SetTriggerInfo]',
                            @Order = N'First',
                            @StmtType = N'INSERT';
    
    EXEC sp_SetTriggerOrder @TriggerName = N'[SchemaName].[tr_SetTriggerInfo]',
                            @Order = N'First',
                            @StmtType = N'UPDATE';
    GO
    

    应该工作,原因如下:

    • 使用 "Context Connection = true;" 的 ConnectString 的 SQLCLR 触发器可以从本地临时表中读取。

    • 本地临时表在嵌套场景中表现如下:

      • 如果父上下文中已经存在临时表,则 DML 语句将看到它并可以与之交互,并且这些更改将能够同时用于附加嵌套级别和父级别(非常方便的功能)
      • 如果父上下文中已经存在一个临时表,并且为相同的临时表名称执行 CREATE TABLE 语句,而不是出错,它将创建该表的 副本,并且父上下文中同名的临时表现在将无法访问(即隐藏)。这意味着,如果您处于第 3 级,那么当该级别结束时,在第 2 级运行的触发器的代码仍将具有第 2 级的值,而不是第 3 级的值。

      这对于CONTEXT_INFO 是不可能的,这就是为什么CONTEXT_INFO 不适用于嵌套场景:级别 3 将覆盖级别 2 的值,因此当控制返回到级别 2 时,它现在在CONTEXT_INFO。鉴于我们只能将单个触发器设置为“第一个”而不能控制哪个触发器是“第二个”(除非您的触发器永远不会超过 3 个,在这种情况下您也可以使用“最后一个”位置),那么您不能保证 SQLCLR 触发器将在“第一个”触发器之后立即触发,并且如果另一个触发器触发修改了具有此触发器设置的表,那么您在 CONTEXT_INFO 中的数据已损坏。


    更新
    实际上,上述临时表解决方案可能不起作用,因为一旦 T-SQL 触发器结束,即 SQLCLR 触发器触发之前,本地临时表可能会消失。下周我还需要测试这个。如果这是实际行为并且本地温度不起作用,那么我有另一个想法应该起作用。

    【讨论】:

    • @Crono 如果您在认为我完成后阅读了我添加的所有内容,这不是坏消息。我发布了一个肯定可以工作的解决方案。在更新其他问题之前,我已经计划在下周进行测试。
    • @Crono 实际上,我的想法可能行不通。请参阅更新部分。
    猜你喜欢
    • 1970-01-01
    • 2012-01-05
    • 2019-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多