为什么在独立应用程序中正常工作的SQL Server CLR函数会导致崩溃?

3
下面这两种方法很相似,只是一种处理空值,而另一种不处理。为了处理空值,它使用SqlString类型并检查“get_IsNull”属性。
为什么第一个方法在SQL CLR内运行时会导致错误“在用户定义的例程或聚合“CheckMailingAddress”执行期间发生了.NET Framework错误。”,而第二个方法则不会?
特别是,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"来实现第一种方法并进行空值检查,但它使用SqlString来利用INullable接口属性"IsNull"和"Value",因为它是通用代码生成器的一部分,其他Sql*类型也可以使用。
简单问题概述:
对于那些被方法体中的MSIL所分散注意力的人们,请忽略它。我重新编译了函数,什么也没做,我的观点是,当输入类型为"SqlString"而不是"string"时,CLR会崩溃并终止,没有错误消息,并且返回值为NULL而不是TRUE。
//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 
}

1
哇,伙计 - 你在用MSIL编写CLR过程?我可以问一下为什么吗?你有尝试看看编译后的C#代码是否能实现相同的功能吗? - Matt Whitfield
是的!应用程序和数据库的统一数据类。单点维护+快速的SQL CLR检查约束函数。 - Triynko
说实话,我认为 MSIL 代码没有任何问题。我认为最近对 SQL Server 的补丁引入了一个错误,导致在使用 SqlString 类型的函数中传输数据时出现问题,因为这些方法在 SQL Server 外部运行良好,并且它们曾经在 SQL Server 内部正常工作。这些方法的 TSQL 函数接口始终将 "nvarchar(4000)" 用作参数类型,而我数据库中唯一不同的是我的字段也是 "nvarchar(4000)",而它们曾经是 "varchar(3000)"。 - Triynko
1
@Triynko:如果看起来太难,那通常就是。 - Mitch Wheat
@Mitch:这很简单。我本来想说服你的,但是像任何东西一样,当你不理解它时,它看起来很复杂。我做的只是建立一个小应用程序来自动生成像这样的一个函数:bool CheckMailingAddress(string field_from_db) {(MailingAddress)field_from_db;return true;}, 然后部署它打包好的程序集,并在TSQL中创建该函数。CheckMailingAddress包装器从数据库约束中调用,当转换为MailingAddress失败时抛出错误,并在否则返回true。这很简单,速度也很快。 - Triynko
显示剩余17条评论
1个回答

5
我找到了问题的来源,并成功解决了,但我对细节不太确定。
在某个时候,我将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中提取,因为在我的DeployDatabaseAssembly中被引用为“typeof(SqlString)”,该程序集针对.NET 4.0。很奇怪,没有任何错误消息或关于与已加载的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,则会立即收到一个“有用的错误消息”,指出“CREATE ASSEMBLY for assembly 'my assembly name' failed because the assembly is built for an unsupported version of the CLR runtime.”。很奇怪的是,当我根本没有设置任何目标平台时,它可以正常部署,但是却没有任何错误消息导致崩溃。
下表总结了上述配置组合和结果:

我并不惊讶问题最后发现在你发布的代码之外。但是顺带一提,你考虑过别人可能需要维护这个程序吗?除非你的性能要求非常高,否则我建议用C#编写,因为会读/写C#的人比会读/写MSIL的人多得多。否则你可能会留下一大块难以理解的代码给公司的下一个开发者。当然,如果这真的是一个要求,那就必须付出这样的代价。 - Ryan
必须拥有绝对击杀速度,并且直接发出 MSIL 确保没有编译器注入任何不优化的代码。该程序实际上完全使用 C# 编写,并使用 ILGenerator 类及其 Emit 方法发出 MSIL 操作码 (System.Reflection.Emit.OpCodes 枚举),因此您永远不必处理 MSIL 语法。发布的 MSIL 是通过在 .NET Reflector 中打开生成的程序集获得的。 - Triynko
关于考虑其他开发人员...你是否意识到这个工具的整个目的是为了让他们的工作更容易。虽然这个工具本身很复杂,但它不是为了更新而设计的,因为它实际上是一个完成的开发工具,我个人会支持或发布为开源。它包装了许多与在SQL Server中查询各种类型的函数、创建这些函数、处理从.NET到SQL数据类型的映射、部署程序集、创建/禁用/启用/检查约束等相关的复杂功能有关的内容。 - Triynko
使用此实用程序,开发人员可以创建一个像这样的新数据类:"public sealed class Username: RegexConstrainedString" 的头文件,并使用此属性标记 "[SqlFunctionCheck(Table="Users",Field="Username")]"。然后,通过简单运行此实用程序,它将自动为他们执行以下操作:生成新的优化程序集,将其部署到数据库以替换旧程序集,创建 "CheckUsername" SQLCLR 函数,在表上创建检查约束(组合调用该函数和任何其他标记应用于同一表的函数),并重新检查所有约束。 - Triynko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接