Este dilema se puede resolver de diversas formas, pero te mostramos la resolución más completa para nosotros.
Solución:
Si estás lidiando con NVARCHAR
/ NCHAR
datos (que se almacenan como UTF-16 Little Endian), entonces usarías el Unicode
codificación, no BigEndianUnicode
. En .NET, UTF-16 se llama Unicode
mientras que otras codificaciones Unicode se denominan por sus nombres reales: UTF7, UTF8 y UTF32. Por eso, Unicode
por sí mismo es Little Endian
Opuesto a BigEndianUnicode
. ACTUALIZAR: Consulte la sección al final sobre UCS-2 y caracteres suplementarios.
En el lado de la base de datos:
SELECT HASHBYTES('MD5', N'è') AS [HashBytesNVARCHAR]
-- FAC02CD988801F0495D35611223782CF
En el lado de .NET:
System.Text.Encoding.ASCII.GetBytes("è")
// D1457B72C3FB323A2671125AEF3EAB5D
System.Text.Encoding.UTF7.GetBytes("è")
// F63A0999FE759C5054613DDE20346193
System.Text.Encoding.UTF8.GetBytes("è")
// 0A35E149DBBB2D10D744BF675C7744B1
System.Text.Encoding.UTF32.GetBytes("è")
// 86D29922AC56CF022B639187828137F8
System.Text.Encoding.BigEndianUnicode.GetBytes("è")
// 407256AC97E4C5AEBCA825DEB3D2E89C
System.Text.Encoding.Unicode.GetBytes("è") // this one matches HASHBYTES('MD5', N'è')
// FAC02CD988801F0495D35611223782CF
Sin embargo, esta pregunta se refiere a VARCHAR
/ CHAR
datos, que es ASCII, por lo que las cosas son un poco más complicadas.
En el lado de la base de datos:
SELECT HASHBYTES('MD5', 'è') AS [HashBytesVARCHAR]
-- 785D512BE4316D578E6650613B45E934
Ya vemos el lado .NET arriba. A partir de esos valores hash, debería haber dos preguntas:
- Por qué no alguna de ellos coinciden con el
HASHBYTES
¿valor? - ¿Por qué el artículo “sqlteam.com” vinculado en la respuesta de @Eric J. muestra que tres de ellos (
ASCII
,UTF7
, yUTF8
) todos coinciden con elHASHBYTES
¿valor?
Hay una respuesta que cubre ambas preguntas: Páginas de códigos. La prueba realizada en el artículo “sqlteam” utilizó caracteres ASCII “seguros” que están en el rango de 0 a 127 (en términos del valor int / decimal) que no varían entre las páginas de códigos. Pero el rango 128-255, donde encontramos el carácter “è”, es el Extendido conjunto que varía según la página de códigos (lo que tiene sentido ya que esta es la razón para tener páginas de códigos).
Ahora intenta:
SELECT HASHBYTES('MD5', 'è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [HashBytes]
-- D1457B72C3FB323A2671125AEF3EAB5D
Que coincide con el ASCII
valor hash (y nuevamente, debido a que el artículo / prueba “sqlteam” usó valores en el rango de 0 a 127, no vieron ningún cambio al usar COLLATE
). Genial, ahora finalmente encontramos una manera de combinar VARCHAR
/ CHAR
datos. ¿Todo está bien?
Bueno en realidad no. Echemos un vistazo a lo que realmente estábamos hash:
SELECT 'è' AS [TheChar],
ASCII('è') AS [TheASCIIvalue],
'è' COLLATE SQL_Latin1_General_CP1255_CI_AS AS [CharCP1255],
ASCII('è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [TheASCIIvalueCP1255];
Devoluciones:
TheChar TheASCIIvalue CharCP1255 TheASCIIvalueCP1255
è 232 ? 63
A ?
? Solo para verificar, ejecute:
SELECT CHAR(63) AS [WhatIs63?];
-- ?
Ah, entonces la página de códigos 1255 no tiene è
personaje, por lo que se traduce como el favorito de todos ?
. Pero entonces, ¿por qué coincidía con el valor hash MD5 en .NET cuando se usa la codificación ASCII? ¿Podría ser que en realidad no estuviéramos igualando el valor hash de è
, sino que coincidían con el valor hash de ?
:
SELECT HASHBYTES('MD5', '?') AS [HashBytesVARCHAR]
-- 0xD1457B72C3FB323A2671125AEF3EAB5D
Sí. los true El juego de caracteres ASCII es solo los primeros 128 caracteres (valores 0 – 127). Y como acabamos de ver, el è
es 232. Entonces, usando el ASCII
la codificación en .NET no es tan útil. Tampoco estaba usando COLLATE
en el lado de T-SQL.
¿Es posible obtener una mejor codificación en el lado de .NET? Sí, utilizando Encoding.GetEncoding (Int32), que permite especificar la página de códigos. La página de códigos a usar se puede descubrir mediante la siguiente consulta (use sys.columns
cuando se trabaja con una columna en lugar de un literal o variable):
SELECT sd.[collation_name],
COLLATIONPROPERTY(sd.[collation_name], 'CodePage') AS [CodePage]
FROM sys.databases sd
WHERE sd.[name] = DB_NAME(); -- replace function with N'db_name' if not running in the DB
La consulta anterior devuelve (para mí):
Latin1_General_100_CI_AS_SC 1252
Entonces, intentemos la página de códigos 1252:
System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES('MD5', 'è')
// 785D512BE4316D578E6650613B45E934
¡Woo hoo! Tenemos un partido para VARCHAR
datos que utilizan nuestra intercalación predeterminada de SQL Server :). Por supuesto, si los datos provienen de una base de datos o un campo configurado en una intercalación diferente, entonces GetEncoding(1252)
podría no funciona y tendrá que encontrar la página de códigos coincidente real utilizando la consulta que se muestra arriba (una página de códigos se usa en muchas intercalaciones, por lo que una intercalación diferente no funciona necesariamente implica una página de códigos diferente).
Para ver cuáles son los posibles valores de la página de códigos y a qué cultura / configuración regional pertenecen, consulte la lista de páginas de códigos aquí (la lista se encuentra en la sección “Comentarios”).
Información adicional relacionada con lo que realmente se almacena en NVARCHAR
/ NCHAR
los campos:
Se puede almacenar cualquier carácter UTF-16 (2 o 4 bytes), aunque el comportamiento predeterminado de las funciones integradas asume que todos los caracteres son UCS-2 (2 bytes cada uno), que es un subconjunto de UTF-16. A partir de SQL Server 2012, es posible acceder a un conjunto de intercalaciones de Windows que admiten los caracteres de 4 bytes conocidos como caracteres suplementarios. Usando una de estas intercalaciones de Windows que terminan en _SC
, ya sea especificado para una columna o directamente en una consulta, permitirá que las funciones integradas manejen correctamente los caracteres de 4 bytes.
-- The database's collation is set to: SQL_Latin1_General_CP1_CI_AS
SELECT N'' AS [SupplementaryCharacter],
LEN(N'') AS [LEN],
DATALENGTH(N'') AS [DATALENGTH],
UNICODE(N'') AS [UNICODE],
LEFT(N'', 1) AS [LEFT],
HASHBYTES('MD5', N'') AS [HASHBYTES];
SELECT N'' AS [SupplementaryCharacter],
LEN(N'' COLLATE Latin1_General_100_CI_AS_SC) AS [LEN],
DATALENGTH(N'' COLLATE Latin1_General_100_CI_AS_SC) AS [DATALENGTH],
UNICODE(N'' COLLATE Latin1_General_100_CI_AS_SC) AS [UNICODE],
LEFT(N'' COLLATE Latin1_General_100_CI_AS_SC, 1) AS [LEFT],
HASHBYTES('MD5', N'' COLLATE Latin1_General_100_CI_AS_SC) AS [HASHBYTES];
Devoluciones:
SupplementaryChar LEN DATALENGTH UNICODE LEFT HASHBYTES
2 4 55393 0x7A04F43DA81E3150F539C6B99F4B8FA9
1 4 165739 0x7A04F43DA81E3150F539C6B99F4B8FA9
Como ves, tampoco DATALENGTH
ni HASHBYTES
Son afectados. Para obtener más información, consulte la página de MSDN para compatibilidad con intercalación y Unicode (específicamente la sección “Caracteres complementarios”).
SQL Server usa UCS-2 en lugar de UTF-8 para codificar datos de caracteres.
Si estuviera utilizando un campo NVarChar, lo siguiente funcionaría:
System.Text.Encoding.Unicode.GetBytes("è"); // Updated per @srutzky's comments
Para obtener más información sobre el hash de SQL y C #, consulte
http://weblogs.sqlteam.com/mladenp/archive/2009/04/28/Comparing-SQL-Server-HASHBYTES-function-and-.Net-hashing.aspx
Eres capaz de respaldar nuestra publicación ejecutando un comentario o puntuándolo te lo agradecemos.