2011-04-13 20 views
8

Parece que SQL Server utiliza Unicode UCS-2, una codificación de caracteres de longitud fija de 2 bytes, para los campos nchar/nvarchar. Mientras tanto, C# utiliza la codificación Unicode UTF-16 para sus cadenas (nota: Algunas personas no consideran que UCS-2 sea Unicode, pero codifica todos los mismos puntos de código que UTF-16 en el subconjunto Unicode 0-0xFFFF, y en lo que respecta a SQL Server, eso es lo más parecido a "Unicode" que admite nativamente en términos de cadenas de caracteres).¿Cuáles son las consecuencias de almacenar una cadena C# (UTF-16) en una columna nvarchar de SQL Server (UCS-2)?

Mientras que UCS-2 codifica los mismos puntos de código básicos que UTF-16 en el plano multilingüe básico (BMP), no reserva ciertos patrones de bits que UTF-16 hace para permitir pares sustitutos.

Si escribo una cadena de C# en un campo de SQL Server nvarchar (UCS-2) y la vuelvo a leer, ¿siempre devolverá el mismo resultado?

Parece que mientras que UTF-16 es un superconjunto de UCS-2 en el sentido de que UTF-16 codifica más puntos de código (por ejemplo, por encima de 0xFFFF), en realidad es un subconjunto de UCS-2 en 2 bytes nivel, ya que es más restrictivo.

Para responder a mi propia pregunta, sospecho que si mi cadena C# contiene puntos de código superiores a 0xFFFF (representados por pares de caracteres), estos serían almacenados y recuperados muy bien en la base de datos, pero si traté de manipularlos en la base de datos (por ejemplo, quizás llamar a TOUPPER o intentar borrar cualquier otro carácter), entonces podría tener algunos problemas para mostrar la cadena más adelante ... a menos que SQL Server tenga funciones que reconozcan pares de sustitución y traten efectivamente las cadenas nchar/nvarchar como UTF-16 .

Respuesta

3

Es todo un poco fudge realmente.

En primer lugar las similitudes

  • El SQL Server nchar/nvarchar/ntext tienda de tipos de datos de texto como una cadena de caracteres de 2 bytes. Realmente no le importa lo que pones en ellos hasta que llegas a hacer la búsqueda y clasificación (luego usa la secuencia de clasificación Unicode apropiada).
  • El tipo de datos CLR String también almacena texto como una cadena de 2 bytes Char s. Tampoco le importa realmente lo que le pongas hasta que llegues a hacer búsquedas y clasificaciones (luego utiliza los métodos apropiados específicos de cada cultura).

Ahora las diferencias

  • .NET le permite acceder a los puntos de código Unicode reales en una cadena de CLR a través de la clase StringInfo.
  • .NET tiene mucho soporte para codificar y decodificar datos de texto en una variedad de codificaciones. Al convertir una secuencia de bytes arbitraria a String, siempre codificará la cadena como UTF-16 (con soporte de plano multilingüe completo).

En resumen, , siempre y cuando usted trata a las dos variables de cadena de servidor SQL CLR y como toda burbujas de texto, a continuación, puede asignar libremente de uno a otro sin pérdida de información. El formato de almacenamiento subyacente es exactamente el mismo, aunque las abstracciones en capas en la parte superior son ligeramente diferentes.

+0

Ok, por lo que la lectura/escritura de una cadena como una entidad completa a un campo nvarchar no causará problemas o pérdida de información, incluso si contiene lo que se interpretaría como pares de sustitución. Ahora, ¿qué tal escribir una cadena C# en una columna char? Sospecho que implicaría alguna interpretación y conversión y causaría la pérdida de datos ... – Triynko

+0

Las columnas de un solo byte tienen una secuencia de intercalación no Unicode definida en ellas, que no solo define las reglas de búsqueda y clasificación, sino también la página de códigos que define qué los personajes están permitidos Cualquier punto de código Unicode que esté asignado a un valor en la página de códigos de la columna se conservará y el resto se descartará. –

+0

¿Desechado ... o reemplazado por un dummy en particular o un byte "sin carácter"? ¿Las páginas de códigos de un solo byte reservan un cierto byte para los caracteres que no son? He visto algunos ejemplos que muestran que los caracteres Unicode no definidos en el espacio de código de destino se reemplazan con el signo de interrogación, pero ¿tal vez solo cómo se muestran los caracteres? – Triynko

4

No espero que tratar el texto como UCS-2 cause muchos problemas.

Las conversiones de casos no deberían ser un problema, porque (AFAIK) no hay asignaciones de casos por encima del BMP (¡excepto el mapeo de identidad, por supuesto!) Y, obviamente, los personajes sustituidos se asignarán a sí mismos.

La supresión de cualquier otro personaje solo está causando problemas. En realidad, hacer este tipo de transformaciones sin tener en cuenta los valores de los caracteres es siempre una actividad peligrosa. Puedo verlo sucediendo legítimamente con truncamientos de cadena. Pero si aparece algún sustituto sin coincidencia en el resultado, esto en sí mismo no es un gran problema . Cualquier sistema que reciba tales datos, y se preocupe, probablemente simplemente reemplace el sustituto sin igual con un personaje de reemplazo, si se molesta en hacer algo al respecto.

Obviamente, la longitud de la cadena va a ser bytes/2 en lugar de número de caracteres, pero el número de caracteres no es un valor muy útil de todos modos, una vez que empiezas a sondear las profundidades de los gráficos Unicode. Por ejemplo, no obtendrá buenos resultados en la visualización monoespaciada una vez que abandone el rango ASCII, debido a la combinación de caracteres, idiomas RTL, caracteres de control direccional, etiquetas y varios tipos de caracteres espaciales. Los puntos altos de código serán el menor de tus problemas.

Por si fuera poco, probablemente debería guardar sus textos cuneiformes en una columna diferente a los nombres del arqueólogo. : D

¡ACTUALIZACIÓN ahora con datos empíricos!

Acabo de ejecutar una prueba para ver qué pasa con las transformaciones de casos. Creé una cadena con la palabra en inglés TEST en mayúscula dos veces primero en script latino, luego en script Deseret. Apliqué una transformación en minúsculas a esta cadena en .NET y en SQL Server.

La versión .NET encerró correctamente todas las letras en ambos scripts. La versión de SQL Server solo bajaba los caracteres latinos y no cambiaba los caracteres Deseret. Esto cumple con las expectativas con respecto al manejo de UTF-16 versos 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(); 
    } 
} 

Salida:

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 

Sólo en caso de que alguien ha instalado una fuente de Deseret, aquí están las cadenas reales para su disfrute:

Original: TEST 
.NET Lower: test 
    DB Lower: test 
+0

Gracias por la respuesta. No estoy de acuerdo con que las conversiones de casos no sean un problema. Por ejemplo, llamar a TOUPPER en una cadena en la base de datos produciría una secuencia de bytes diferente que llamar a ToUpper en una cadena en C#, precisamente porque si hay un par suplente presente, el TOUPPER TSQL mostrará en mayúsculas cada secuencia de 2 bytes del par individualmente (para que la segunda secuencia de 2 bytes caiga en el rango de BMP 0-0xFFFF y potencialmente sea mayúscula), mientras que CLR String.ToUpper probablemente tenga en cuenta el par sustituto y produzca un nuevo par que represente la letra mayúscula . – Triynko

+0

Probablemente podría hacer una pregunta completamente diferente como "¿Qué cadenas transformadas son neutrales?". Cambiar la caja, encontrar la longitud del carácter, comparar/clasificar la cuerda, invertirla, etc. probablemente no sea un sustituto neutral, pero ¿qué hay de recortar? Creo que tal vez no haya ninguno, por lo que estoy de acuerdo con su afirmación de que "hacer este tipo de transformaciones sin tener en cuenta los valores del personaje es siempre una actividad peligrosa". – Triynko

+0

@Triynko: los puntos del código sustituto se asignan específicamente para que sean transparentes en UCS-2. Intentar mayúsculas, ya sea un sustituto líder o un sustituto final siempre se correlacionará con el carácter original, ya que no hay conversión de caso definida para esos puntos de código. Si suponemos que hay conversiones de casos definidas en los planos altos (lo que dudo), CLR y TSQL realizarán la conversión de forma diferente, pero ninguna de las operaciones generará datos basura (ya que TSQL no modificará esos caracteres). ... –

Cuestiones relacionadas