这是SqlCLR的有效用例吗?调整存储过程结果

时间:2018-10-07 12:55:57

标签: sql-server tsql stored-procedures sqlclr

我已经实现了我在下面描述的CLR存储过程,并且运行良好。但是我不确定我是否真的需要CLR来完成此任务,或者不确定中间层解决方案是否具有同样的性能和可维护性。

多年来,该公司已在现有代码库中累积了500多个搜索存储过程。现在他们要我编写一个适用于所有这些存储过程的聚合引擎。他们系统中的每个搜索存储过程都遵循类似的格式,因此我知道如何以正确的参数等方式编程调用它们。

我不想以任何方式修改每个搜索存储过程。我想做的是首先将存储过程的结果插入到临时表中。然后,我可以通过查询临时表来运行聚合引擎。

问题是,在SQL Server中,除非您知道存储过程结果的精确模式,否则您将无法插入存储过程的结果。但这实际上是不可能的,因为存储过程可以根据参数返回不同的结果模式。

因此,为了确保存储过程将返回我期望的精确模式,我创建了一个“ SP_Wrapper” CLR存储过程。在这个包装器中,我调用一个存储过程,并将每个记录“调整”到我期望的模式。然后,我返回调整后的结果集。

然后,我可以在知道该模式正确的情况下将其插入到临时表中。

现在,可以说我在中间层调整了结果。我将不得不首先将结果返回到中间层。遍历它们,修改每条记录,然后分别插入或批量复制。

这似乎是正确的选择,但现在我必须部署此CLR存储过程。我真的在这里收获很多吗?

using (var conn = new SqlConnection("context connection=true"))
{
        conn.Open();

        //load result table schema
        resultColumns = SqlSchema.getTempTableMeta(conn, resultTableName);

        //load parameter table schema - may not exist
        var hasParams = !String.IsNullOrEmpty(paramTableName);
        parameters = SqlSchema.getTempTableMeta(conn, paramTableName);

        SqlCommand command;
        SqlDataReader reader = null;

        ///Load Parameter Values
        if (hasParams)
        {
            command = conn.CreateCommand();
            command.CommandText = $@"if( object_id('tempdb..{paramTableName}') is not null) select top 1 * from {paramTableName};";
            command.CommandType = CommandType.Text;
            reader = command.ExecuteReader();

            using (reader)
            {
                while (reader.Read())
                {
                    foreach (var p in parameters)
                    {
                        var val = reader[p.Name];

                        if (!String.IsNullOrWhiteSpace(val?.ToString()))
                            parameter_values[p.Name] = val;
                    }
                }
            }
        }

        SqlDataRecord record = new SqlDataRecord(resultColumns.ToArray());

        //////mark the beginning of the result set
        SqlContext.Pipe.SendResultsStart(record);

        command = conn.CreateCommand();
        command.CommandType = CommandType.StoredProcedure;
        command.CommandText = spName;

        foreach (var p in parameters)
        {
            if (parameter_values.ContainsKey(p.Name))
                command.Parameters.Add(
                    new SqlParameter
                    {
                        ParameterName = p.Name,
                        SqlDbType = p.SqlDbType,
                        Value = parameter_values[p.Name]
                    }
                );
        }

        var cmdReader = command.ExecuteReader();

        using (cmdReader)
        {
            while (cmdReader.Read())
            {
                int sequence = 0;
                foreach (var resultColumn in resultColumns)
                {
                    var resultColumnValue = cmdReader[resultColumn.Name];

                    var t = resultColumn.SqlDbType;

                    resultColumnValue = SqlSchema.Convert(resultColumnValue, SqlSchema.sqlTypeMap[t]);

                    record.SetValue(sequence, resultColumnValue);
                    sequence++;
                }
                SqlContext.Pipe.SendResultsRow(record);
            }
        }

        // Mark the end of the result-set.
        SqlContext.Pipe.SendResultsEnd();

        conn.Close();
    }

2 个答案:

答案 0 :(得分:3)

原则上,此解决方案是有意义的。您正在使用SQL CLR作为转换为已知模式的适配器。您编写的代码看起来也很有效。

缺点是SQL CLR代码比普通代码更难编写,更难以测试和部署。

此权衡是否适合您取决于您​​的性能需求和开发人员生产力的需求。这种数据复制是否真的花费了那么多时间,值得触摸SQL CLR?可能会或可能不会。

另一种更快的解决方案是为必须调用的每个过程生成SQL代码。不要手写。而是使用工具确定该过程的确切方案,并输出完美的T-SQL,该T-SQL将正确格式的数据直接传递到正确的目的地。

此工具确实可以是生成代码然后执行的SQL CLR过程。或者,它可能是基于C#的代码生成器。

答案 1 :(得分:1)

我会说这取决于:

  1. 从存储过程返回的数据在做什么?

    问题中张贴的代码缺少一些内容,因此,如果要获取全部或部分返回的列,则并不清楚。当前方法的一个好处是,当将结果转储到临时表中时,您可以忽略不感兴趣的列。生成T-SQL代码以在纯T-SQL中执行INSERT...EXEC不会允许您过滤掉整个列;无论您是否愿意,都必须将所有列插入目标表。

  2. 此界面对那些搜索过程还有其他潜在用途吗?

    SQLCLR方法的一个好处是它更通用。如果此功能包含在应用程序代码中,则只有应用程序代码可以使用它。您将无法在SQL Agent Job中使用它(不调用应用程序代码,这可能需要编写指向同一库的控制台应用程序,或使该库成为PowerShell模块)。您将无法在通过数据库邮件发送自动报告的proc中使用它。您将无法轻松地将其用途扩展到尚未被请求的其他区域。如果可能的话,只是要考虑一下。

  3. 对此我不是100%肯定,但是您当前的方法可能可以让您绕开执行INSERT...EXEC的限制(如果您正在执行proc(或其子流程之一(如果有的话)具有INSERT...EXEC

    (我现在没有时间进行测试,但是当我这样做时,如果发现它没有绕过限制,我将删除这一点。)

尽管从部署/ CI角度来看,SQLCLR不像T-SQL那样简单明了,但这也不是不可能。当然,当需要适当地处理安全性时(例如,使用基于签名的登录而不是启用TRUSTWORTHY),Visual Studio / SSDT确实不容易实现自动化的部署,如果使用SQL Server 2017或较新的。为了解决这个问题,我演示了两种与Visual Studio / SSDT或独立使用的相似方法,在下面的两个博客文章中对此进行了讨论:

  1. SQLCLR vs. SQL Server 2017, Part 2: “CLR strict security” – Solution 1-比第3部分解决方案2(如下)要多的步骤,但是非常适合现有项目,因为它几乎不需要更改现有解决方案甚至部署过程(事实上,这是有效的我为SQL#项目所做的工作是在安装脚本的开头添加了3个简单的步骤)。此解决方案使用非对称密钥作为签名。
  2. SQLCLR vs. SQL Server 2017, Part 3: “CLR strict security” – Solution 2。此解决方案使用证书进行签名。

这两个解决方案的目标不仅是与Visual Studio / SSDT一起使用,而且还产生一个独立的T-SQL脚本。 T-SQL脚本没有外部引用:不引用任何DLL文件,也不引用任何.snk / .cer / .pfx文件。这使脚本可以完全移植,因此可以更轻松地在任何持续集成设置中工作:-)。

有关一般使用SQLCLR的更多信息,请访问:SQLCLR Info