如何通过SqlDataReader获取结果集中字符串字段的排序规则?

时间:2016-02-04 01:20:37

标签: .net sql-server collation sqldatareader sqlclr

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

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

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

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

  • 通过LCID类的GetLocaleId方法公开SqlDataReader。但该方法标记为internal,因此我无法访问它。它似乎只在一个地方使用:SqlBulkCopy
  • 将结果集结构提供给SqlPipe encoding方法,该方法用于SQLCLR存储过程和触发器。
  • 根据具体情况,在SqlDataReader的{​​{1}}方法中设置正确的public。但是,虽然该方法为TextReader,但Encoding类没有UNSAFE的属性;编码信息仅在内部使用。

更新

澄清:希望有一种方法来获取这些信息,而不需要大会完全信任/ PERMISSION_SET。预期用途是在Console App和SQLCLR对象(存储过程,函数等)中运行的代码。如果程序集(加载到SQL Server中时)需要EXTERNAL_ACCESS UNSAFE,那么这是可以接受的。但要求将SQLCLR程序集标记为Latin1_General_100_BIN2将不起作用。

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

更新2

使用Reflection,按照@ Jonathan的Send(SqlDataReader),可以调用“内部”GetLocaleId方法,它确实返回正确的LCID。但是,在SQLCLR对象中使用此代码时,如果程序集未标记为UNSAFE,则会出现以下异常:

  

Ms 6522,Level 16,State 1,Line 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文章GetTextReader(),它甚至提到了(强调添加):

  

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

不幸的是,要求大会UNSAFE违反要求。

而且,说实话,这只是整体难题的一部分。如第一个UPDATE部分所述,目标是让成为真正的排序规则名称,而且目前似乎并不存在。因此,我向Microsoft发送了以下建议:

answer

1 个答案:

答案 0 :(得分: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