如何知道 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
)的问题在于,如果满足以下任一条件,它就不起作用:
CONTEXT_INFO 已被用于其他目的。
一个触发器可以更新一个 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 之前引发火灾。我下周还需要测试这个。如果这是实际行为并且本地温度不起作用,那么我有另一个应该可行的想法。
我有一个数据库,其中的数据必须有条件地复制到同一服务器上不同数量的数据库中。接收方 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
)的问题在于,如果满足以下任一条件,它就不起作用:
CONTEXT_INFO 已被用于其他目的。
一个触发器可以更新一个 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 之前引发火灾。我下周还需要测试这个。如果这是实际行为并且本地温度不起作用,那么我有另一个应该可行的想法。