为啥在 SQL Server CLR 中运行的一个函数可能会导致崩溃,而在独立应用程序中运行良好?

Posted

技术标签:

【中文标题】为啥在 SQL Server CLR 中运行的一个函数可能会导致崩溃,而在独立应用程序中运行良好?【英文标题】:Why might one function run in SQL Server CLR cause a crash, while working fine in a standalone app?为什么在 SQL Server CLR 中运行的一个函数可能会导致崩溃,而在独立应用程序中运行良好? 【发布时间】:2011-05-03 19:38:07 【问题描述】:

下面这两种方法是相似的,除了一种处理空值而另一种不处理。为了处理空值,它使用 SqlString 类型并检查“get_IsNull”属性。

为什么第一个在 SQL CLR 中运行时会导致错误 "A .NET Framework error occurred during execution of user-defined routine or aggregate "CheckMailingAddress": .",而第二个不会?

特别是TSQL错误是"Msg 10329, Level 16, State 49, Line 1 .Net Framework execution was aborted."

.method public hidebysig static bool CheckMailingAddress(valuetype [System.Data]System.Data.SqlTypes.SqlString param0) cil managed

   .maxstack 8
    L_0000: ldarga.s param0
    L_0002: nop 
    L_0003: nop 
    L_0004: call instance bool [System.Data]System.Data.SqlTypes.SqlString::get_IsNull()
    L_0009: brfalse L_0010
    L_000e: ldc.i4.1 
    L_000f: ret 
    L_0010: ldarga.s param0
    L_0012: nop 
    L_0013: nop 
    L_0014: call instance string [System.Data]System.Data.SqlTypes.SqlString::get_Value()
    L_0019: call class DatabaseValues.MailingAddress DatabaseValues.MailingAddress::op_Explicit(string)
    L_001e: pop 
    L_001f: ldc.i4.1 
    L_0020: ret 


.method public hidebysig static bool CheckMailingAddress(string param0) cil managed

   .maxstack 8
    L_0000: ldarg.0 
    L_0001: call class DatabaseValues.CheckMailingAddress DatabaseValues.CheckMailingAddress::op_Explicit(string)
    L_0006: pop 
    L_0007: ldc.i4.1 
    L_0008: ret 

请记住,据我所知,MSIL 是正确的,因为这两种方法在独立应用程序中调用时都有效。只有在 SQL CLR 中调用时,两者中的第一个才会崩溃。在 SQL CLR 中,该函数是使用“nvarchar(4000)”类型定义的,据我所知,它应该与 SqlString 配合得很好。

我可能也可以使用“string”实现第一个方法,并且仍然进行 null 检查,但它使用 SqlString 来利用 INullable 接口属性“IsNull”和“Value”,因为它是通用代码的一部分可以使用其他 Sql* 类型的生成器。

简单问题总结:

对于那些被方法体中的 MSIL 分心的人;忽略它。我重新编译了函数什么都不做,我的观点是,当输入类型是“SqlString”而不是“string”时,CLR 爆炸并终止,没有错误消息,返回值为 NULL 而不是真的。

//Crashes when input parameter is "SqlString"
.method public hidebysig static bool CheckMailingAddress(valuetype [System.Data]System.Data.SqlTypes.SqlString param0) cil managed

   .maxstack 8
    L_001f: ldc.i4.1 
    L_0020: ret 


//Doesn't Crash when input parameter is "string"
.method public hidebysig static bool CheckMailingAddress(string param0) cil managed

   .maxstack 8 
    L_0007: ldc.i4.1 
    L_0008: ret 

【问题讨论】:

您是否尝试将 Visual Studio 调试器附加到此?另外,您能否包含原始语言(即 C#、VB 等)而不是 MSIL 的代码? MSIL 是原始语言。是的,我已经附加了调试器。它失败了,没有超出我给出的特定错误消息,并且没有在 CLR 中遇到任何类型的断点。 哇老兄 - 你在 MSIL 中编写 CLR 过程?我能问为什么吗?你有没有试过看看有哪些编译过的 C# 做同样的事情? 是的!应用程序和数据库的统一数据类。单点维护+快速SQL CLR检查约束功能。 @Triynko:如果看起来太难,通常是这样。 【参考方案1】:

我找到了问题的根源,并且能够解决它,但我不确定细节。

在某个时候,我将我的 DeployDatabaseAssembly 项目切换为面向 .NET 4.0,而 AssemblyBuilder 必须也生成了一个面向 .NET 4.0 的程序集。将项目切换到目标 .NET 3.5 解决了这个问题。

有趣的是,包含我所有数据类型的源 DLL (database.dll) 仍然以 .NET 3.5 为目标,并且故意保留这种方式是因为我知道 SQL Server 目前只支持 CLR 2.0,这实际上使其与.NET 4.0,因为 .NET 4.0 似乎需要 CLR 4.0。使用 ILMerge,我将包含生成函数的动态程序集与我现有的 (.NET 3.5) database.dll 结合起来。这最终导致混合程序集 .NET 4.0 程序集主要基于 .NET 3.5 功能和类。奇怪的是,我能够让使用基本“String”和“int”类型参数的函数工作,但是 SqlString 类型导致崩溃......显然是因为它是从 .NET 4.0 System.Data.dll 中提取的,因为它在面向 .NET 4.0 的 DeployDatabaseAssembly 中被引用为“typeof(SqlString)”。奇怪的是,它在没有任何类型的错误消息或没有任何关于它与加载的 SQL CLR 模块不兼容的警告的情况下崩溃。

我希望我知道一种方法来强制在 .NET 4.0 应用程序中运行的 AssemblyBuilder 生成面向 .NET 3.5 的程序集...

更新:问题彻底解决

我专注于 ILMerge 的输出并继续将 DeployDatabaseAssembly 切换回 .NET 4.0。顺便说一句,我在项目中使用 ILMerge 作为参考,因为它是一个 .NET 程序集。

通过像这样设置 ILMerge 选项:

ILMerge merger = new ILMerge();
merger.SetTargetPlatform( "v2", @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client");

生成的 DLL 会部署到 SQL Server(和之前一样),但这次它实际上运行没有错误。

有趣的是,如果我仅将目标平台路径中的“v3.5”替换为“v4.0”并尝试将程序集部署到 SQL Server,那么我会在部署期间立即收到一条有用的错误消息“为程序集“我的程序集名称”创建程序集失败,因为程序集是为不受支持的 CLR 运行时版本构建的。”。奇怪的是,当我根本不设置任何目标平台时,它可以正常部署,但在没有任何错误消息的情况下崩溃。

此表总结了以上配置组合和结果:

【讨论】:

问题最终出现在您发布的代码之外并不令我感到惊讶。但作为旁注,您是否考虑过其他人可能必须维护这一事实?除非您的性能要求是它必须是绝对杀手级的速度,否则我建议在 C# 中执行此操作,因为可以读/写 C# 的人比读/写 MSIL 的人要多。您可能会为您公司的下一个开发人员留下大量难以理解的代码。话又说回来,如果真的是要求,那就是必须付出的代价。 它必须是绝对杀手级的速度,并且直接发出 MSIL 可确保在任何情况下都不会有编译器在混合中注入非最佳代码。该程序实际上完全用 C# 编写,并使用 ILGenerator 类及其 Emit 方法来发出 MSIL 操作码(System.Reflection.Emit.OpCodes 枚举),因此实际上不必处理 MSIL 语法。发布的 MSIL 是通过在 .NET Reflector 中打开生成的程序集获得的。 至于其他开发人员的考虑...您是否意识到此实用程序的全部目的是让他们更轻松。虽然该实用程序本身很复杂,但它并不是要更新的东西,因为它确实是一个完整的开发工具,我个人会支持或作为开源发布。它封装了许多复杂的功能,与查询 SQL Server 中的各种类型的函数、创建这些函数、处理从 .NET 到 SQL 数据类型的映射、部署程序集、创建/禁用/启用/检查约束等有关。 使用这个实用程序,开发人员可以创建一个新的数据类,其标题如下:“公共密封类用户名:RegexConstrainedString”,并用这样的属性标记它“[SqlFunctionCheck(Table="Users ",Field="用户名")]"。然后,只需运行此实用程序,它将自动为它们执行以下操作:生成新的优化程序集,将其部署到数据库中替换旧程序集,创建“CheckUsername”SQLCLR 函数,在表上创建检查约束(结合对函数和任何其他标记为应用于同一个表),并重新检查所有约束。

以上是关于为啥在 SQL Server CLR 中运行的一个函数可能会导致崩溃,而在独立应用程序中运行良好?的主要内容,如果未能解决你的问题,请参考以下文章

SQL CLR 在 SQL Server 中随机失败

SQL Server 中的 CLR 程序集 C#

CLR SQL Server UDF 问题

如何在 SQL Server 的 clr 存储过程中执行动态 .net 代码

使用 SQL-CLR 函数在 SQL Server 中验证加密数据的安全性

在 SQL Server 2012 SSDT 解决方案中引用 CLR 项目