Каковы последствия хранения строки C # (UTF-16) в столбце SQL Server nvarchar (UCS-2)? - PullRequest
8 голосов
/ 14 апреля 2011

Похоже, что SQL Server использует Unicode UCS-2 , 2-байтовую кодировку символов фиксированной длины, для полей nchar/nvarchar. Между тем, C # использует Unicode UTF-16 для своих строк (примечание: некоторые люди не считают UCS-2 Unicode, но он кодирует все те же кодовые точки, что и UTF-16 в подмножестве Unicode). 0-0xFFFF, и, что касается SQL Server, это самая близкая вещь к «Юникоду», который он изначально поддерживает в терминах символьных строк.)

Хотя UCS-2 кодирует те же базовые кодовые точки, что и UTF-16 в базовой многоязычной плоскости (BMP), он не резервирует определенные битовые комбинации, которые UTF-16 делает для учета суррогатных пар.

Если я напишу строку C # в поле SQL Server nvarchar (UCS-2) и прочту ее обратно, будет ли это всегда возвращать один и тот же результат?

Кажется, что, хотя UTF-16 является надмножеством UCS-2 в том смысле, что UTF-16 кодирует больше кодовых точек (например, выше 0xFFFF), на самом деле это подмножество UCS-2 на 2-байтовом уровне. , поскольку это более ограничительно.

Чтобы ответить на мой собственный вопрос, я подозреваю, что если моя строка C # содержит кодовые точки выше 0xFFFF (представленные парами символов), они будут просто отлично сохраняться и извлекаться в базе данных, но если я попытаюсь манипулировать ими в базы данных (например, возможно, вызывая TOUPPER или пытаясь исключить все остальные символы), тогда я мог бы столкнуться с некоторыми проблемами при отображении строки позже ... если в SQL Server нет функций, которые подтверждают суррогатные пары и эффективно обрабатывают строки nchar/nvarchar как UTF- 16.

Ответы [ 2 ]

4 голосов
/ 14 апреля 2011

Я не ожидаю, что обработка текста как UCS-2 вызовет много проблем.

Преобразования регистра не должны быть проблемой, потому что (AFAIK) нет никаких отображений регистра выше BMP (кромеконечно же, отображение личности!), и, очевидно, суррогатные персонажи будут сопоставляться с самим собой.

Отбеливание каждого другого персонажа просто вызывает проблемы.На самом деле такие трансформации без учета ценностей персонажей - это всегда опасное занятие.Я вижу, что это происходит законно с усечением строки.Но если в результате появятся какие-то непревзойденные суррогаты, это само по себе не является огромной проблемой.Любая система, которая получает такие данные и заботится о них, вероятно, просто заменит непревзойденный суррогат заменяющим символом, если она вообще не хочет ничего с этим делать.

Очевидно, длина строки будет в байтах / 2а не количество символов, но количество символов не очень полезно в любом случае, как только вы начнете изучать глубину кодовых диаграмм Юникода.Например, вы не получите хороших результатов в моноширинном отображении после выхода из диапазона ASCII из-за сочетания символов, языков RTL, символов управления направлением, тегов и нескольких видов пробелов.Высокие значения кода будут наименьшей из ваших проблем.

Просто чтобы быть в безопасности, вы, вероятно, должны хранить свои клинописные тексты в другом столбце, чем имена археолога.: D

ОБНОВЛЕНИЕ теперь с эмпирическими данными!

Я только что выполнил тест, чтобы увидеть, что происходит с преобразованиями регистра.Я создал строку с английским словом TEST в верхнем регистре дважды - сначала на латинице, затем на десерте.Я применил преобразование в нижнем регистре к этой строке в .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????
3 голосов
/ 14 апреля 2011

Это все немного выдумка на самом деле.

Первое сходство

  • В типах данных SQL Server nchar / nvarchar / ntext текст хранится в виде строки из 2-байтовых символов. На самом деле все равно, что вы вставите в них, пока вы не приступите к поиску и сортировке (тогда он использует соответствующую последовательность сортировки Unicode).
  • Тип данных CLR String также хранит текст в виде строки размером 2 байта Char с. На самом деле, все равно, что вы вставите в него, пока вы не приступите к поиску и сортировке (тогда он использует соответствующие методы, специфичные для данной культуры).

Теперь различия

  • .NET позволяет получить доступ к фактическим кодовым точкам Unicode в строке CLR через класс StringInfo .
  • .NET имеет тонну поддержки кодирования и декодирования текстовых данных в различных кодировках. При преобразовании произвольного потока байтов в String он всегда будет кодировать строку как UTF-16 (с полной поддержкой многоязычной плоскости).

Короче говоря, , пока вы рассматриваете строковые переменные CLR и SQL Server как целые объекты текста , вы можете свободно присваивать одно другому без потери информации. Базовый формат хранилища точно такой же, даже если абстракции, расположенные сверху, немного отличаются.

...