如何知道TriggerContext当前更新的表?

时间:2015-09-04 18:48:40

标签: c# .net sql-server sqlclr

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

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

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

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

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

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

1 个答案:

答案 0 :(得分: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触发器。

  3. 但是,使用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语句将看到它并且可以与它进行交互,并且这些更改将能够同时具有其他嵌套级别以及父级别(非常方便的特性)< / LI>
      • 如果父上下文中已存在临时表,并且对同一个临时表名执行CREATE TABLE语句,而不是出错,则会创建该表的 new 副本现在,父上下文中的同名临时表将不可访问(即隐藏)。这意味着,如果您处于第3级,那么当该级别完成时,在级别2中运行的触发器的代码仍将具有级别2值,而不是级别3值。

      CONTEXT_INFO这是不可能的,这就是CONTEXT_INFO不适用于嵌套场景的原因:级别3将覆盖级别2的值,所以当控件返回到级别2时,它现在有CONTEXT_INFO中的错误值。鉴于我们只能将单个Trigger设置为&#34; First&#34;而不是控制哪一个是&#34;第二&#34; (除非你从来没有超过3个触发器,在这种情况下你也可以使用&#34; Last&#34;位置),那么你不能保证SQLCLR触发器会在&#34; First&#之后立即触发34;触发器,如果​​另一个触发器触发修改其上具有此触发器设置的表,那么CONTEXT_INFO中的数据已损坏。

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