我已经实现了我在下面描述的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();
}
答案 0 :(得分:3)
原则上,此解决方案是有意义的。您正在使用SQL CLR作为转换为已知模式的适配器。您编写的代码看起来也很有效。
缺点是SQL CLR代码比普通代码更难编写,更难以测试和部署。
此权衡是否适合您取决于您的性能需求和开发人员生产力的需求。这种数据复制是否真的花费了那么多时间,值得触摸SQL CLR?可能会或可能不会。
另一种更快的解决方案是为必须调用的每个过程生成SQL代码。不要手写。而是使用工具确定该过程的确切方案,并输出完美的T-SQL,该T-SQL将正确格式的数据直接传递到正确的目的地。
此工具确实可以是生成代码然后执行的SQL CLR过程。或者,它可能是基于C#的代码生成器。
答案 1 :(得分:1)
我会说这取决于:
从存储过程返回的数据在做什么?
问题中张贴的代码缺少一些内容,因此,如果要获取全部或部分返回的列,则并不清楚。当前方法的一个好处是,当将结果转储到临时表中时,您可以忽略不感兴趣的列。生成T-SQL代码以在纯T-SQL中执行INSERT...EXEC
不会允许您过滤掉整个列;无论您是否愿意,都必须将所有列插入目标表。
此界面对那些搜索过程还有其他潜在用途吗?
SQLCLR方法的一个好处是它更通用。如果此功能包含在应用程序代码中,则只有应用程序代码可以使用它。您将无法在SQL Agent Job中使用它(不调用应用程序代码,这可能需要编写指向同一库的控制台应用程序,或使该库成为PowerShell模块)。您将无法在通过数据库邮件发送自动报告的proc中使用它。您将无法轻松地将其用途扩展到尚未被请求的其他区域。如果可能的话,只是要考虑一下。
对此我不是100%肯定,但是您当前的方法可能可以让您绕开执行INSERT...EXEC
的限制(如果您正在执行proc(或其子流程之一(如果有的话)具有INSERT...EXEC
。
(我现在没有时间进行测试,但是当我这样做时,如果发现它没有绕过限制,我将删除这一点。)
尽管从部署/ CI角度来看,SQLCLR不像T-SQL那样简单明了,但这也不是不可能。当然,当需要适当地处理安全性时(例如,使用基于签名的登录而不是启用TRUSTWORTHY
),Visual Studio / SSDT确实不容易实现自动化的部署,如果使用SQL Server 2017或较新的。为了解决这个问题,我演示了两种与Visual Studio / SSDT或独立使用的相似方法,在下面的两个博客文章中对此进行了讨论:
这两个解决方案的目标不仅是与Visual Studio / SSDT一起使用,而且还产生一个独立的T-SQL脚本。 T-SQL脚本没有外部引用:不引用任何DLL文件,也不引用任何.snk / .cer / .pfx文件。这使脚本可以完全移植,因此可以更轻松地在任何持续集成设置中工作:-)。
有关一般使用SQLCLR的更多信息,请访问:SQLCLR Info