Se hai a che fare con NVARCHAR
/ NCHAR
dati (che sono archiviati come UTF-16 Little Endian ), quindi utilizzeresti Unicode
codifica, non BigEndianUnicode
. In .NET, UTF-16 è chiamato Unicode
mentre altre codifiche Unicode sono indicate con i loro nomi effettivi:UTF7, UTF8 e UTF32. Quindi, Unicode
di per sé è Little Endian
al contrario di BigEndianUnicode
. AGGIORNAMENTO: Consulta la sezione alla fine relativa a UCS-2 e ai caratteri supplementari.
Lato database:
SELECT HASHBYTES('MD5', N'è') AS [HashBytesNVARCHAR]
-- FAC02CD988801F0495D35611223782CF
Sul lato .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
Tuttavia, questa domanda riguarda VARCHAR
/ CHAR
data, che è ASCII, e quindi le cose sono un po' più complicate.
Lato database:
SELECT HASHBYTES('MD5', 'è') AS [HashBytesVARCHAR]
-- 785D512BE4316D578E6650613B45E934
Vediamo già il lato .NET sopra. Da quei valori hash dovrebbero sorgere due domande:
- Perché non nessuno di loro corrispondono a
HASHBYTES
valore? - Perché l'articolo "sqlteam.com" collegato nella risposta di @Eric J. mostra che tre di loro (
ASCII
,UTF7
eUTF8
) tutti corrispondono aHASHBYTES
valore?
C'è una risposta che copre entrambe le domande:Code Pages. Il test eseguito nell'articolo "sqlteam" ha utilizzato caratteri ASCII "sicuri" compresi nell'intervallo 0 - 127 (in termini di valore int/decimale) che non variano tra le pagine codici. Ma l'intervallo 128 - 255 -- dove troviamo il carattere "è" -- è il esteso set che varia in base alla Code Page (il che ha senso in quanto questo è il motivo per avere le Code Page).
Ora prova:
SELECT HASHBYTES('MD5', 'è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [HashBytes]
-- D1457B72C3FB323A2671125AEF3EAB5D
Corrisponde a ASCII
valore hash (e ancora, poiché l'articolo / test "sqlteam" utilizzava valori nell'intervallo 0 - 127, non hanno visto alcuna modifica quando si utilizza COLLATE
). Ottimo, ora abbiamo finalmente trovato un modo per abbinare VARCHAR
/ CHAR
dati. Tutto bene?
Beh, non proprio. Diamo un'occhiata a cosa stavamo effettivamente effettuando l'hashing:
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];
Resi:
TheChar TheASCIIvalue CharCP1255 TheASCIIvalueCP1255
è 232 ? 63
Un ?
? Solo per verificare, esegui:
SELECT CHAR(63) AS [WhatIs63?];
-- ?
Ah, quindi Code Page 1255 non ha il è
carattere, quindi viene tradotto come il ?
preferito da tutti . Ma allora perché corrispondeva al valore hash MD5 in .NET quando si utilizzava la codifica ASCII? Potrebbe essere che non stavamo effettivamente abbinando il valore hash di è
, ma invece corrispondevano al valore hash di ?
:
SELECT HASHBYTES('MD5', '?') AS [HashBytesVARCHAR]
-- 0xD1457B72C3FB323A2671125AEF3EAB5D
Sì. Il vero set di caratteri ASCII è solo i primi 128 caratteri (valori 0 - 127). E come abbiamo appena visto, il è
è 232. Quindi, usando il ASCII
la codifica in .NET non è così utile. Né stava usando COLLATE
sul lato T-SQL.
È possibile ottenere una migliore codifica sul lato .NET? Sì, utilizzando Encoding.GetEncoding(Int32), che consente di specificare la tabella codici. La tabella codici da utilizzare può essere individuata utilizzando la query seguente (usa sys.columns
quando si lavora con una colonna invece di un valore letterale o variabile):
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 query sopra restituisce (per me):
Latin1_General_100_CI_AS_SC 1252
Quindi, proviamo Pagina codice 1252:
System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES('MD5', 'è')
// 785D512BE4316D578E6650613B45E934
Woo hoo! Abbiamo una corrispondenza per VARCHAR
dati che utilizzano le nostre regole di confronto SQL Server predefinite :). Ovviamente, se i dati provengono da un database o da un campo impostato su regole di confronto diverse, allora GetEncoding(1252)
potrebbe non funziona e dovrai trovare la tabella codici effettiva corrispondente utilizzando la query mostrata sopra (una tabella codici viene utilizzata in molte regole di confronto, quindi non è necessariamente implicano una Code Page diversa).
Per vedere quali sono i possibili valori della Code Page e a quale cultura/località si riferiscono, consulta l'elenco delle Code Page qui (l'elenco si trova nella sezione "Osservazioni").
Informazioni aggiuntive relative a ciò che è effettivamente memorizzato in NVARCHAR
/ NCHAR
campi:
È possibile memorizzare qualsiasi carattere UTF-16 (2 o 4 byte), sebbene il comportamento predefinito delle funzioni integrate presuppone che tutti i caratteri siano UCS-2 (2 byte ciascuno), che è un sottoinsieme di UTF-16. A partire da SQL Server 2012, è possibile accedere a un set di regole di confronto di Windows che supportano i caratteri a 4 byte noti come caratteri supplementari. Utilizzando una di queste regole di confronto di Windows che terminano con _SC
, specificato per una colonna o direttamente in una query, consentirà alle funzioni integrate di gestire correttamente i caratteri a 4 byte.
-- 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];
Resi:
SupplementaryChar LEN DATALENGTH UNICODE LEFT HASHBYTES
𨝫 2 4 55393 � 0x7A04F43DA81E3150F539C6B99F4B8FA9
𨝫 1 4 165739 𨝫 0x7A04F43DA81E3150F539C6B99F4B8FA9
Come puoi vedere, né DATALENGTH
né HASHBYTES
sono interessati. Per ulteriori informazioni, vedere la pagina MSDN per le regole di confronto e il supporto Unicode (in particolare la sezione "Caratteri supplementari").