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

How to know TriggerContext's currently updated table?

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

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

复制键值
internal static void ReplicateToInstances()
{
    if (!IsTableWriteTriggerContextAvailable()) throw new InvalidOperationException();

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

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

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

应该很明显,但不幸的是事实并非如此。 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. 一个触发器可以更新一个 table,它上面还有一个需要知道其父对象名称的 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

这个应该工作,原因如下:

  • A SQLCLR 触发器,使用 "Context Connection = true;" 的 ConnectString 可以从本地临时 tables.

  • 中读取
  • 本地临时 table 在嵌套场景中的行为如下:

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

    这在 CONTEXT_INFO 中是不可能的,这就是为什么 CONTEXT_INFO 不适用于嵌套场景:级别 3 将覆盖级别 2 的值,因此当控制权返回到级别 2 时,它CONTEXT_INFO 现在有错误的值。鉴于我们只能将一个触发器设置为 "First" 而不能控制哪个是 "second" (除非你永远不会有超过 3 个触发器,在这种情况下你也可以使用 "Last" 位置),那么你不能保证 SQLCLR 触发器会在 "First" 触发器之后立即触发,并且如果另一个触发器触发修改了 table 上面有这个触发器设置,那么您在 CONTEXT_INFO.

  • 中的数据已损坏

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