基于.net ComputeHash的SQL CLRfunction不适用于Cyrrilic

我编写了以下SQL CLR函数,以便散列大于8000字节的字符串值( T-SQL内置HASHBYTES函数的输入值的限制):

 [SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true)] public static SqlBinary HashBytes(SqlString algorithm, SqlString value) { HashAlgorithm algorithmType = HashAlgorithm.Create(algorithm.Value); if (algorithmType == null || value.IsNull) { return new SqlBinary(); } else { byte[] bytes = Encoding.UTF8.GetBytes(value.Value); return new SqlBinary(algorithmType.ComputeHash(bytes)); } } 

它适用于拉丁字符串。 例如,以下哈希值是相同的:

 SELECT dbo.fn_Utils_GetHashBytes ('MD5', 'test'); -- 0x098F6BCD4621D373CADE4E832627B4F6 SELECT HASHBYTES ('MD5', 'test'); -- 0x098F6BCD4621D373CADE4E832627B4F6 

问题是它不适用于西里尔字符串。 例如:

 SELECT dbo.fn_Utils_GetHashBytes ('MD5 ', N'даровете на влъхвите') -- NULL SELECT HashBytes ('MD5 ',N'даровете на влъхвите') -- 0x838B1B625A6074B2BE55CDB7FCEA2832 SELECT dbo.fn_Utils_GetHashBytes ('SHA256', N'даровете на влъхвите') -- 0xA1D65374A0B954F8291E00BC3DD9DF655D8A4A6BF127CFB15BBE794D2A098844 SELECT HashBytes ('SHA2_256',N'даровете на влъхвите') -- 0x375F6993E0ECE1864336E565C8E14848F2A4BAFCF60BC0C8F5636101DD15B25A 

我为MD5获取NULL ,尽管如果它作为控制台应用程序执行,代码将返回值。 谁能说出我做错了什么?


另外,我从这里得到了这个function,其中一条评论说:

小心CLR SP参数被静默截断为8000字节 – 我不得不用[SqlFacet(MaxSize = -1)]标记参数,否则8000之后的字节将被忽略!

但我已经测试了这个并且工作正常。 例如,如果我生成一个8000字节字符串的散列和相同字符串的第二个散列加一个符号,我得到的散列是不同的。

 DECLARE @A VARCHAR(MAX) = '8000 bytes string...' DECLARE @B VARCHAR(MAX) = @A + '1' SELECT LEN(@A), LEN(@B) SELECT IIF(dbo.fn_Utils_GetHashBytes ('MD5', @A + '1') = dbo.fn_Utils_GetHashBytes ('MD5', @B), 1, 0) -- 0 

我应该担心吗?

  Encoding.UTF8.GetBytes(...) 

SQL Server没有UTF-8的概念。 使用UCS-2(UTF-16)或ASCII。 使用的编码必须与您传递给HASHBYTES的编码相匹配。 您可以很容易地看到HASHBYTES将以不同的方式散列VARCHARNVARCHAR

 select HASHBYTES('MD5', 'Foo') -- 0x1356C67D7AD1638D816BFB822DD2C25D select HASHBYTES('MD5', N'Foo') -- 0xB25FF0AD90D09D395090E8A29FF4C63C 

最好的方法是更改​​SQLCLR函数以接受字节,而不是字符串,并处理调用者中的转换为VARBINARY

  SELECT dbo.fn_Utils_GetHashBytes ('MD5', CAST(N'даровете на влъхвите' AS VARBINARY(MAX)); 

仅供参考SQL Server 2016解除了对HASHBYTES的8000字节限制:

对于SQL Server 2014及更早版本 ,允许的输入值限制为8000字节。

有关解释您看到差异的原因的详细演练,请参阅我对以下问题的回答:

TSQL md5哈希与C#.NET md5不同

对于那些不希望自己编译和部署的人来说,这个函数可以在SQLCLR函数,存储过程等的SQL#库的免费版本中找到(我是其中的创建者,但是Util_HashUtil_HashBinary等等)其他人,都是免费的)。 问题中显示的内容与SQL#中的两个Util_Hash *函数之间存在一个区别:问题中显示的函数采用NVARCHAR / SqlString输入参数,而SQL#函数采用VARBINARY / SqlBinary输入。 不同之处是:

  • 接受VARBINARY输入也适用于二进制源数据(文件,图像,加密值等)
  • 虽然接受VARBINARY输入确实需要在函数调用中执行CONVERT(VARBINARY(MAX), source_string)的额外步骤,这样做可以保留用于VARCHAR数据的任何代码页。 虽然不经常使用,但在使用非Unicode数据时这可能很方便。

关于其他职位的警告:

小心CLR SP参数被静默截断为8000字节 – 我不得不用[SqlFacet(MaxSize = -1)]标记参数,否则8000之后的字节将被忽略!

然而你没有遇到同样的事情:这是由于SSDT如何为SQLCLR对象生成T-SQL包装器对象的变化。 在早期版本(特别是VS 2013之前的Visual Studio附带版本)中,默认行为是对SqlChars使用NVARCHAR(MAX) ,对SqlString NVARCHAR(4000) 。 但是在某些时候(我不想说VS 2013因为Visual Studio和SSDT是独立的产品,即使VS带有SSDT),默认情况也改为使用NVARCHAR(MAX)用于SqlCharsSqlString 。 发布警告的人(2013-02-06)必须使用早期版本的SSDT。 尽管如此,明确并使用[SqlFacet(MaxSize = -1)]并不会伤害(甚至是一种好的做法)。

关于if (algorithmType == null || value.IsNull)逻辑:由于任何一个为NULL都应返回NULL ,因此最好删除该逻辑并使用CREATE FUNCTION语句的WITH RETURNS NULL ON NULL INPUT选项。 但是,遗憾的是,此选项不支持任何SSDT构造(即没有SqlFacet )。 因此,为了启用此选项,您可以创建一个Post-Deployment SQL脚本(将在主脚本之后自动部署),该脚本将发出具有所需定义的ALTER FUNCTION 。 投票支持我的Connect建议以原生支持此选项也没有什么坏处: 在SqlFunctionAttribute中实现OnNullCall属性,以便在NULL INPUT SQLCLR上为RETURNS NULL 。 在实际层面上,性能增益主要出现在你为@algorithm参数传递大值的情况下,但是@algorithm在某种程度上是NULL ,因此你不会最终使用@algorithm的值。 使用RETURNS NULL ON NULL INPUT选项的原因是当您调用传入SqlStringSqlBinary的SQLCLR函数时,整个值将被复制到App Domain的内存中。 如果你事先知道你不会使用它,那就是你不需要浪费的时间,内存和CPU :-)。 即使传入较小的值,您也可能会看到非常频繁调用的函数的增益。


关于警告和测试的附注:SQLCLR不支持VARCHAR ,仅支持NVARCHAR 。 因此,由于SSDT没有自动使用NVARCHAR(MAX) ,因此限制为4000个字符,因此从未有过8000的限制。 因此,如果存在差异,那么首先只能测试4000和4001个字符。