9

似乎 SQL Server对字段使用 Unicode UCS-2,一种 2 字节固定长度字符编码。nchar/nvarchar同时,C# 对其字符串使用 Unicode UTF-16编码(注意:有些人不认为 UCS-2 是 Unicode,但它在 Unicode 子集 0-0xFFFF 中编码所有与 UTF-16 相同的代码点,并且作为就 SQL Server 而言,这是与它在字符串方面本机支持的“Unicode”最接近的东西。)

虽然 UCS-2 在基本多语言平面 (BMP) 中编码与 UTF-16 相同的基本代码点,但它不保留 UTF-16 允许代理对的某些位模式。

如果我将 C# 字符串写入 SQL Server nvarchar(UCS-2) 字段并将其读回,这是否总是返回相同的结果?

似乎 UTF-16 是 UCS-2 的超集,因为 UTF-16 编码更多的代码点(例如高于 0xFFFF),但它实际上是 2 字节级别的 UCS-2 的子集,因为它是更严格。

为了回答我自己的问题,我怀疑如果我的 C# 字符串包含高于 0xFFFF 的代码点(由字符对表示),这些将在数据库中很好地存储和检索,但如果我试图在数据库中操作它们(例如也许调用 TOUPPER 或尝试清除所有其他字符),然后我可能会在稍后显示字符串时遇到一些问题......除非 SQL Server 具有确认代理对并将nchar/nvarchar字符串有效地视为 UTF-16 的函数。

4

2 回答 2

4

这真的有点胡说八道。

首先是相似之处

  • SQL Server nchar//nvarchar数据ntext类型将文本存储为 2 字节字符的字符串。在您进行搜索和排序之前,它并不真正关心您在其中放入了什么(然后它使用适当的 Unicode 排序规则序列)。
  • CLRString数据类型还将文本存储为 2 字节Char的字符串。在您进行搜索和排序(然后它使用适当的文化特定方法)之前,它也并不真正关心您放入了什么。

现在的差异

  • .NET 允许您通过StringInfo类访问 CLR 字符串中的实际 Unicode 代码点。
  • .NET 大量支持以各种编码方式对文本数据进行编码和解码。将任意字节流转换为 aString时,它总是将字符串编码为 UTF-16(具有完整的多语言平面支持)。

简而言之,只要您将 CLR 和 SQL Server 字符串变量都视为整个 blob 文本,那么您可以自由地从一个到另一个分配,而不会丢失信息。底层存储格式完全相同,尽管顶层的抽象层略有不同。

于 2011-04-13T20:48:41.497 回答
4

我不认为将文本视为 UCS-2 会导致很多问题。

大小写转换应该不是问题,因为(AFAIK)在 BMP 之上没有大小写映射(当然,除了恒等映射!),而且,显然,代理字符将映射到自己。

空白所有其他字符只是自找麻烦。实际上,在不考虑字符值的情况下进行此类转换始终是一项危险的活动。我可以看到它通过字符串截断合法地发生。但是,如果结果中出现任何不匹配的代理,这本身并不是一个问题。任何接收到此类数据并关心这些数据的系统都可能只是用替换字符替换不匹配的代理,如果它根本不费心做任何事情的话。

显然,字符串长度将是字节/2 而不是字符数,但是一旦您开始深入了解 Unicode 代码图表的深度,字符数无论如何都不是一个非常有用的值。例如,由于字符、RTL 语言、方向控制字符、标签和几种空格字符的组合,一旦离开 ASCII 范围,您将不会在等宽显示中获得良好的结果。高代码点将是您遇到的最少的问题。

为了安全起见,您可能应该将楔形文字存储在与考古学家姓名不同的列中。:D

现在用经验数据更新!

我刚刚进行了一个测试,看看案例转换会发生什么。我用大写的英文单词 TEST 创建了一个字符串,第一次是拉丁文,然后是 Deseret 文。我在 .NET 和 SQL Server 中对这个字符串应用了小写转换。

.NET 版本正确地小写了两个脚本中的所有字母。SQL Server 版本仅将拉丁字符小写,而保留 Deseret 字符不变。这符合处理 UTF-16 和 UCS-2 的预期。

using System;
using System.Data.SqlClient;

class Program
{
    static void Main(string[] args)
    {
        string myDeseretText = "TEST\U00010413\U00010407\U0001041D\U00010413";
        string dotNetLower = myDeseretText.ToLower();
        string dbLower = LowercaseInDb(myDeseretText);

        Console.WriteLine("  Original: {0}", DisplayUtf16CodeUnits(myDeseretText));
        Console.WriteLine(".NET Lower: {0}", DisplayUtf16CodeUnits(dotNetLower));
        Console.WriteLine("  DB Lower: {0}", DisplayUtf16CodeUnits(dbLower));
        Console.ReadLine();
    }

    private static string LowercaseInDb(string value)
    {
        SqlConnectionStringBuilder connection = new SqlConnectionStringBuilder();
        connection.DataSource = "(local)";
        connection.IntegratedSecurity = true;
        using (SqlConnection conn = new SqlConnection(connection.ToString()))
        {
            conn.Open();
            string commandText = "SELECT LOWER(@myString) as LoweredString";
            using (SqlCommand comm = new SqlCommand(commandText, conn))
            {
                comm.CommandType = System.Data.CommandType.Text;
                comm.Parameters.Add("@myString", System.Data.SqlDbType.NVarChar, 100);
                comm.Parameters["@myString"].Value = value;
                using (SqlDataReader reader = comm.ExecuteReader())
                {
                    reader.Read();
                    return (string)reader["LoweredString"];
                }
            }
        }
    }

    private static string DisplayUtf16CodeUnits(string value)
    {
        System.Text.StringBuilder sb = new System.Text.StringBuilder();

        foreach (char c in value)
            sb.AppendFormat("{0:X4} ", (int)c);
        return sb.ToString();
    }
}

输出:

  Original: 0054 0045 0053 0054 D801 DC13 D801 DC07 D801 DC1D D801 DC13
.NET Lower: 0074 0065 0073 0074 D801 DC3B D801 DC2F D801 DC45 D801 DC3B
  DB Lower: 0074 0065 0073 0074 D801 DC13 D801 DC07 D801 DC1D D801 DC13

以防万一有人安装了 Deseret 字体,以下是供您欣赏的实际字符串:

  Original: TEST
.NET Lower: test
  DB Lower: test
于 2011-04-13T20:49:09.547 回答