SQL CLR触发器 - 获取源表

时间:2017-04-11 12:55:43

标签: sql-server sqlclr

我正在使用Microsoft SQL Server 2012中的SQL CLR触发器创建数据库同步引擎。这些触发器不会调用存储过程或函数(从而可以访问INSERTED和DELETED伪表,但无法访问@@ PROCID)。

差异here,供参考。

此"同步引擎"使用映射表来确定此同步作业的表和字段映射的内容。为了确定目标表和字段(来自我的映射表),我需要从触发器本身获取源表名。我在Stack Overflow和其他网站上遇到过很多答案,说这是不可能的。但是,我发现了一个提供线索的website

潜在解决方案:

using (SqlConnection lConnection = new SqlConnection(@"context connection=true")) {
    SqlCommand cmd = new SqlCommand("SELECT object_name(resource_associated_entity_id) FROM sys.dm_tran_locks WHERE request_session_id = @@spid and resource_type = 'OBJECT'", lConnection);
    cmd.CommandType = CommandType.Text;
    var obj = cmd.ExecuteScalar();
}

这确实会返回正确的表名。

问题:

我的问题是,这种潜在的解决方案有多可靠? @@ spid实际上是否仅限于此单个触发器执行?或者其他同步触发器是否可能在此进程ID中重叠?它是否能够在数据库中多次执行相同和/或不同的触发器?

从这些网站中,似乎过程ID实际上仅限于开放式连接,它不会重叠:herehere和{ {3}}

这是获取源表的安全方法吗?

为什么吗

我已经注意到类似的问题,但都没有针对我的具体情况的有效答案(除了那个)。这些网站上的大多数评论都会问"为什么?",为了先发制人,这就是原因:

此同步引擎在单个数据库上运行,可以将更改推送到目标表,使用用户定义的转换转换数据,自动源到目标类型转换和解析,甚至可以使用CSharpCodeProvider执行也存储在那些用于转换数据的映射表。它已经构建,非常强大,并且具有良好的性能指标,可用于我们正在做的事情。我现在正在尝试构建它以允许1:n表更改(包括扩展表需要与' master'表相同的ID)并且我正在尝试" genericise&#34 ;代码。以前每个触发器都有一个"目标表"定义硬编码在其中我使用我的映射表来确定源。现在我想获取源表并使用我的映射表来确定所有目标表。这用于中等负载环境,并将更改推送到变更订单簿"变更订单簿"一个单独的服务器进程选择完成CRUD操作。

修改

正如评论中所提到的,上面列出的查询完全是" iffy"。它经常(例如在SQL Server重启后)返回系统对象,如syscolpars或sysidxstats。但是,似乎在dm_tran_locks表中总是有一个关联的资源类型' RID' (行ID)具有相同的object_name。到目前为止,我当前可靠运行的查询如下(如果更改或在高负载测试下无效,将更新):

select t1.ObjectName FROM (
    SELECT object_name(resource_associated_entity_id) as ObjectName
    FROM sys.dm_tran_locks WHERE resource_type = 'OBJECT' and request_session_id = @@spid
) t1 inner join (
    SELECT OBJECT_NAME(partitions.OBJECT_ID) as ObjectName
    FROM sys.dm_tran_locks 
    INNER JOIN sys.partitions ON partitions.hobt_id = dm_tran_locks.resource_associated_entity_id
    WHERE resource_type = 'RID'
) t2 on t1.ObjectName = t2.ObjectName

如果情况总是如此,我将不得不在测试期间找到它。

2 个答案:

答案 0 :(得分:0)

  

这种潜在的解决方案有多可靠?

虽然我没有时间设置测试用例以证明它不起作用,但我发现这种方法(甚至考虑到编辑部分中的查询)“iffy”(即不保证 总是可靠)。

主要关注点是:

  • 级联(无论是否递归)触发器执行
  • 用户(即明确/隐含)交易
  • 子流程(即EXECsp_executesql

这些场景允许同时锁定多个对象。

  

@@ SPID实际上是否仅限于此单次触发器执行?或者其他同步触发器是否可能在此进程ID中重叠?

和(来自对问题的评论):

  

我想我可以使用sys.partitions加入我的查询并获得一个dm_trans_lock,其类型为“RID”,其对象名称将与我原始查询中的对象名称匹配。

这就是为什么它不应该完全可靠:会话ID(即@@SPID)对于该连接上的所有请求都是常量)。因此,所有子流程(即EXEC调用,sp_executesql,触发器等)都将位于同一@@SPID / session_id。因此,在子流程和用户事务之间,您可以非常轻松地获取多个资源上的锁,所有资源都在相同的会话ID上。

我说“资源”而不是“OBJECT”甚至“RID”的原因是锁可能发生在:行,页面,键,表,模式,存储过程,数据库本身等等。不止一件事可以被认为是“OBJECT”,你可能会有页锁而不是行锁。

  

它是否能够在数据库中多次执行相同和/或不同的触发器?

只要这些执行发生在不同的会话中,那么它们就不是问题。

所有这些都可以,我可以看到简单的测试表明您当前的方法是可靠的。但是,添加更详细的测试也应该很容易,这些测试包括首先在另一个表上执行某些DML的显式事务,或者在一个表上使用触发器在其中一个表上执行某些DML等。

不幸的是,没有内置机制提供@@PROCID对T-SQL触发器所做的相同功能。我想出了一个应该允许获取SQLCLR触发器的父表(考虑到这些不同的问题)的方案,但是没有机会测试它。它需要使用T-SQL触发器(设置为“第一个”触发器)来设置SQLCLR触发器可以发现的信息。

可以使用CONTEXT_INFO构建更简单的表单,如果您还没有将其用于其他内容(如果您还没有“第一个”触发器集)。在这种方法中,您仍然会创建一个T-SQL触发器,然后使用sp_settriggerorder将其设置为“第一个”触发器。在此触发器中SET CONTEXT_INFO指向作为@@PROCID父级的表名。然后,您可以在SQLCLR触发器中的上下文连接上读取CONTEXT_INFO()。如果有多个级别的触发器,那么CONTEXT INFO的值将被覆盖,因此读取该值必须是您在每个SQLCLR触发器中执行的第一件事。

答案 1 :(得分:0)

这是一个旧线程,但这是一个常见问题解答,我想我有一个更好的解决方案。本质上,它通过对列名进行散列并将散列与具有哈希表的CLR触发器的表的散列进行比较,从而使用插入或删除表的架构来查找基表。

下面的代码段-在某个时候,我可能会将整个解决方案放在Git上(当触发器触发时,它将向Azure Service Bus发送消息)。

private const string colqry = "select top 1 * from inserted union all select top 1 * from deleted";
private const string hashqry = "WITH cols as ( "+
                                        "select top 100000 c.object_id, column_id, c.[name] "+
                                        "from sys.columns c "+
                                            "JOIN sys.objects ot on (c.object_id= ot.parent_object_id and ot.type= 'TA') " +
                                        "order by c.object_id, column_id ) "+
                                    "SELECT s.[name] + '.' + o.[name] as 'TableName', CONVERT(NCHAR(32), HASHBYTES('MD5',STRING_AGG(CONVERT(NCHAR(32), HASHBYTES('MD5', cols.[name]), 2), '|')),2) as 'MD5Hash' " +
                                    "FROM cols "+
                                        "JOIN sys.objects o on (cols.object_id= o.object_id) "+
                                        "JOIN sys.schemas s on (o.schema_id= s.schema_id) "+
                                    "WHERE o.is_ms_shipped = 0 "+
                                    "GROUP BY s.[name], o.[name]";

    public static void trgSendSBMsg()
    {
        string table = "";
        SqlCommand cmd;
        SqlDataReader rdr;
        SqlTriggerContext trigContxt = SqlContext.TriggerContext;
        SqlPipe p = SqlContext.Pipe;

        using (SqlConnection con = new SqlConnection("context connection=true"))
        {
            try
            {
                con.Open();
                string tblhash = "";
                using (cmd = new SqlCommand(colqry, con))
                {
                    using (rdr = cmd.ExecuteReader(CommandBehavior.SingleResult))
                    {
                        if (rdr.Read())
                        {
                            MD5 hash = MD5.Create();
                            StringBuilder hashstr = new StringBuilder(250);
                            for (int i=0; i < rdr.FieldCount; i++)
                            {
                                if (i > 0) hashstr.Append("|");
                                hashstr.Append(GetMD5Hash(hash, rdr.GetName(i)));
                            }
                            tblhash = GetMD5Hash(hash, hashstr.ToString().ToUpper()).ToUpper();
                        }
                        rdr.Close();
                    }
                }
                using (cmd = new SqlCommand(hashqry, con))
                {
                    using (rdr = cmd.ExecuteReader(CommandBehavior.SingleResult))
                    {
                        while (rdr.Read())
                        {
                            string hash = rdr.GetString(1).ToUpper();
                            if (hash == tblhash)
                            {
                                table = rdr.GetString(0);
                                break;
                            }
                        }
                        rdr.Close();
                    }
                }
                if (table.Length == 0)
                {
                    p.Send("Error: Unable to find table that CLR trigger is on. Message not sent!");
                    return;
                }
….

HTH