【问题标题】:How to get the Collation of string Fields in a Result Set via SqlDataReader?如何通过 SqlDataReader 获取结果集中字符串字段的排序规则?
【发布时间】:2016-05-13 11:09:44
【问题描述】:

SqlDataReader 检索结果集时,可以使用GetSchemaTable 方法获取该结果集的大部分元数据。然而,缺少的一件事是字符串字段的排序规则(即CHARVARCHARNCHARNVARCHARSQL_VARIANT——如果包含字符串类型——甚至是已弃用的TEXTNTEXT)。有什么办法可以得到这些信息吗?

虽然在技术上可以调用具有LCIDSqlCompareOptions 属性的GetSqlString 方法,但这些属性返回与数据库 的默认排序规则相关的值,这可能或者可能不是任何特定字段的排序规则(即使大多数人似乎只是假设它是相同的)或表达式(由于Collation Precedence)。这些属性仅在源数据类型为 SQL_VARIANT 时提供准确的信息,这并不是很有帮助。

没有这个额外信息(至少是等同于区域设置的“LCID”)的问题在于,虽然所有字符,无论源编码如何,都可以在 .NET 中表示而不会丢失(因为 .NET 字符串是 UTF-16 Little Endian),在将结果集中的字段与其他字符串进行比较时,无法确定要使用的 Locale。

每个字符串字段的排序规则信息绝对是通过 TDS 流从 SQL Server 发送到客户端的result set meta-data 的一部分。使用:

  • 通过SqlDataReader 类的GetLocaleId 方法公开LCID。但是该方法被标记为internal,所以我无权访问它。它似乎只用在一个地方:SqlBulkCopy
  • SqlPipeSend(SqlDataReader) 方法提供结果集结构,该方法用于SQLCLR 存储过程和触发器。
  • SqlDataReaderGetTextReader()方法中,根据情况设置合适的encoding。然而,虽然该方法是public,但TextReader 类没有Encoding 的属性;编码信息仅在内部使用。

更新

澄清一下:希望有一种方法来获取此信息,而无需完全信任程序集/UNSAFE。预期用途是在控制台应用程序和 SQLCLR 对象(存储过程、函数等)中运行的代码。如果程序集(加载到 SQL Server 时)需要有一个 PERMISSION_SETEXTERNAL_ACCESS,这是可以接受的。但是要求将 SQLCLR 程序集标记为 UNSAFE 将不起作用。

最终的最高理想是获取 SQL Server 中存在的完整排序规则名称(例如 Latin1_General_100_BIN2),在 SQL Server 中将程序集标记为 SAFE

更新 2

使用反射,根据@Jonathan 的answer,可以调用“内部”GetLocaleId 方法,它确实返回正确的 LCID。但是,在 SQLCLR 对象中使用此代码时,如果 Assembly 未标记为 UNSAFE,则会出现以下异常:

消息 6522,第 16 级,状态 1,第 9 行
在执行用户定义的例程或聚合“GetFieldCollat​​ion”期间发生 .NET Framework 错误:

System.MethodAccessException:尝试通过方法“UserDefinedFunctions.GetFieldCollat​​ion(System.Data.SqlTypes.SqlString, System.Data.SqlTypes.SqlBoolean)”访问方法“System.Data.SqlClient.SqlDataReader.GetLocaleId(Int32)”失败.

 System.MethodAccessException:
在 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, RuntimeMethodHandleInternal 方法, RuntimeType parent, UInt32 invocationFlags)
在 System.RuntimeMethodHandle.PerformSecurityCheck(Object obj, IRuntimeMethodInfo 方法, RuntimeType parent, UInt32 invocationFlags)
在 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] 参数, CultureInfo 文化)
在 System.Reflection.MethodBase.Invoke(Object obj, Object[] 参数)

查看这篇 MSDN 文章 Walkthrough: Emitting Code in Partial Trust Scenarios,它甚至提到(强调添加):

向沙盒域添加 RestrictedMemberAccess
...
例如,主机可能授予 Internet 应用程序 Internet 权限和 RMA,以便 Internet 应用程序可以发出访问其自己程序集中的私有数据的代码。 由于访问仅限于同等或较低信任的程序集,因此 Internet 应用程序无法访问完全受信任的程序集的成员,例如 .NET Framework 程序集。

很遗憾,要求大会为UNSAFE 违反了要求。

而且,老实说,这实际上只是整个难题的一部分。如第一个 UPDATE 部分所述,目标是让这个 成为真正的排序规则名称,而目前任何地方似乎都不存在。因此,我向微软发送了以下建议:

通过 SqlDataReader 公开 SQL Server 结果集的排序信息(由于 visualstudio.uservoice.com 被关闭,链接不再有效)

更新 3

澄清一下:理想情况下会有一个方法或属性返回用于给定字符串字段的精确排序规则(例如Latin1_General_100_CI_AS_KS_WS_SC)。但是,仅仅公开LCIDSqlCompareOptions 可能还不够好,因为这些属性没有传达以下信息:

  • 排序规则版本(目前 80(即未指定)、90、100 或 140 / 甚至替代 0、1、2 或 3 就足够了)
  • _BIN_BIN2
  • CodePage
  • _VSS(变量选择器敏感;从 SQL Server 2017 开始,通过日语版 140 排序规则)
  • _SC(Supplementary Character Aware - 即允许内置函数将 NVARCHAR 数据处理为 UTF-16 而不是 UCS-2)
  • SQL Server 排序规则与 Windows 排序规则
  • SortID(仅用于 SQL Server 排序规则)
  • _UTF8VARCHAR 数据的 UTF-8 编码;SQL Server 2019 中的新增功能)

至少需要公开在表格数据流 (TDS) 中发送的附加信息。该信息是:

  • 排序规则版本(目前 80(即未指定)、90、100 或 140 / 甚至替代 0、1、2 或 3 就足够了)
  • _BIN_BIN2
  • SortID(仅用于 SQL Server 排序规则)
  • _UTF8VARCHAR 数据的 UTF-8 编码;SQL Server 2019 中的新增功能)

SortID,因为它只用于 SQL Server 排序规则,所以我们得到:

  • SQL Server 排序规则与 Windows 排序规则

SortID 或 LCID(取决于排序规则的类型),可用于派生:

  • CodePage

这仍然使以下选项目前下落不明:

  • _VSS(变量选择器敏感;从 SQL Server 2017 开始,通过日语版 140 排序规则)
  • _SC(Supplementary Character Aware - 即允许内置函数将 NVARCHAR 数据处理为 UTF-16 而不是 UCS-2)

所以,也许目前这在技术上是不可能的,但我想我会问一下,以防万一我遗漏了什么。对我来说,这个信息尚不可用,这似乎很奇怪。

【问题讨论】:

    标签: .net sql-server collation sqldatareader sqlclr


    【解决方案1】:

    您可以使用反射或表达式树轻松访问内部属性(用于性能)

    var method = typeof (SqlDataReader).GetMethod("GetLocaleId", BindingFlags.NonPublic | BindingFlags.Instance);
    using (var conn = new SqlConnection(My.Config.ConnectionStrings.SqlConnection))
    {
        using (var command = new SqlCommand("SELECT TOP 0 * FROM TestTable", conn))
        {
            conn.Open();
            using (SqlDataReader reader = command.ExecuteReader())
            {
                var schema = reader.GetSchemaTable();
    
                // 1033 = Latin1_General_CI_AS (confirmed)
                var collation2 = method.Invoke(reader, new object[] { 2 });
    
                // 1048 = Romanian_CI_AS (Current : SQL_Romanian_CP1250_CI_AS, close enough!)
                var collation3 = method.Invoke(reader, new object[] { 3 });
            }
        }
    }
    

    整理:https://msdn.microsoft.com/en-us/library/ms143508(v=sql.105).aspx

    编辑

    您可以通过反射或表达式树访问所有内部、私有或任何其他内容。

    您也可以使用Eval Expression.NET 使其更加轻松!

    // using Z.Expressions;
    
    var getLocalIdCompiled = Eval.Compile<Func<SqlDataReader, int, int>>("reader.GetLocaleId(value)", "reader", "value");
    
    using (var conn = new SqlConnection(My.Config.ConnectionStrings.SqlConnection))
    {
        using (var command = new SqlCommand("SELECT TOP 0 * FROM TestTable", conn))
        {
            conn.Open();
            using (SqlDataReader reader = command.ExecuteReader())
            {
                var schema = reader.GetSchemaTable();
    
                var collation2 = getLocalIdCompiled(reader, 2);
                var collation3 = getLocalIdCompiled(reader, 3);
            }
        }
    }
    

    免责声明:我是项目的所有者Eval Expression.NET

    【讨论】:

    • 非常有趣。谢谢 :-)。让我试试看。另外,是否可以以相同的方式读取内部和/或私有变量?
    • 乔纳森:感谢您的更新。我已经能够测试原始建议并且它确实有效(因此 +1),但它需要将程序集(在 SQLCLR 中使用时)设置为不安全。这不是最初的要求,所以我更新了问题以包含它。另外,我会说,如果我们期望值 1250 并收到 1048,那么这还不够接近 ;-)。但是,1048 是正确的答案。 1250 是代码页,而不是 LCID:SELECT COLLATIONPROPERTY(N'SQL_Romanian_CP1250_CI_AS', 'LCID')
    • 是的,不幸的是,这个编译器需要不安全的权限(Eval SQL.NET 中使用的编译器可以在安全模式下运行,但暂时不公开)。我仍然不确定您的问题缺少什么。您可以使用反射(安全权限)获取 LCID,我相信没有办法,您无需“硬编码”或进行数据库往返即可找到排序规则名称。
    • 你好。我没有使用编译器。我刚刚做了var method = typeof(SqlDataReader).GetMethod("GetLocaleId", BindingFlags.NonPublic | BindingFlags.Instance); var collation2 = method.Invoke(_Reader, new object[] { 0 }); 我以为那是在使用反射?您是说有一种方法可以在 SAFE 程序集中运行 GetLocaleId 吗?如果是这样,请您提供一个例子。抱歉,我对 Reflection 所做的工作不多。
    • 这是反射及其在 SAFE 大会中的工作。您评论中的方法和我的第一个示例可以获取“GetLocaleId”值......我们肯定在这个问题上彼此不理解;)
    猜你喜欢
    • 2015-01-03
    • 1970-01-01
    • 2012-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-31
    • 2023-03-17
    • 2011-01-25
    相关资源
    最近更新 更多