<TL; DR> Il problema è piuttosto semplice, in realtà: non stai facendo corrispondere la codifica dichiarata (nella dichiarazione XML) con il tipo di dati del parametro di input. Se hai aggiunto manualmente <?xml version="1.0" encoding="utf-8"?><test/>
alla stringa, dichiarando SqlParameter
di essere di tipo SqlDbType.Xml
o SqlDbType.NVarChar
ti darebbe l'errore "impossibile cambiare la codifica". Quindi, durante l'inserimento manuale tramite T-SQL, poiché hai cambiato la codifica dichiarata in utf-16
, stai chiaramente inserendo una VARCHAR
stringa (non preceduta da una "N" maiuscola, quindi una codifica a 8 bit, come UTF-8) e non una NVARCHAR
stringa (preceduta da una "N" maiuscola, da cui la codifica UTF-16 LE a 16 bit).
La correzione avrebbe dovuto essere semplice come:
- Nel primo caso, quando si aggiunge la dichiarazione che indica
encoding="utf-8"
: semplicemente non aggiungere la dichiarazione XML.
- Nel secondo caso, quando si aggiunge la dichiarazione che dichiara
encoding="utf-16"
: o
- semplicemente non aggiungere la dichiarazione XML, OR
- aggiungi semplicemente una "N" al tipo di parametro di input:
SqlDbType.NVarChar
invece di SqlDbType.VarChar
:-) (o eventualmente passa anche all'uso SqlDbType.Xml
)
(La risposta dettagliata è sotto)
Tutte le risposte qui sono troppo complicate e non necessarie (indipendentemente dai 121 e 184 voti positivi per le risposte di Christian e Jon, rispettivamente). Potrebbero fornire codice funzionante, ma nessuno di loro risponde effettivamente alla domanda. Il problema è che nessuno ha capito veramente la domanda, che in ultima analisi riguarda il funzionamento del tipo di dati XML in SQL Server. Niente contro quelle due persone chiaramente intelligenti, ma questa domanda ha poco a che fare con la serializzazione in XML. Il salvataggio dei dati XML in SQL Server è molto più semplice di quanto implicito qui.
Non importa come viene prodotto l'XML purché si seguano le regole su come creare dati XML in SQL Server. Ho una spiegazione più approfondita (incluso un codice di esempio funzionante per illustrare i punti delineati di seguito) in una risposta a questa domanda: come risolvere l'errore "impossibile cambiare la codifica" quando si inserisce XML in SQL Server , ma le basi sono:
- La dichiarazione XML è facoltativa
- Il tipo di dati XML memorizza le stringhe sempre come UCS-2 / UTF-16 LE
- Se il tuo XML è UCS-2 / UTF-16 LE, allora:
- passare i dati come
NVARCHAR(MAX)
o XML
/ SqlDbType.NVarChar
(maxsize = -1) o SqlDbType.Xml
, o se si utilizza una stringa letterale, deve essere preceduta da una "N" maiuscola.
- se si specifica la dichiarazione XML, deve essere "UCS-2" o "UTF-16" (nessuna differenza reale qui)
- Se il tuo XML è codificato a 8 bit (ad es. "UTF-8" / "iso-8859-1" / "Windows-1252"), allora:
- è necessario specificare la dichiarazione XML SE la codifica è diversa dalla tabella codici specificata dalle regole di confronto predefinite del database
- è necessario passare i dati come
VARCHAR(MAX)
/ SqlDbType.VarChar
(maxsize = -1), oppure se si utilizza una stringa letterale allora deve non essere preceduto da un maiuscola "N".
- Qualunque sia la codifica a 8 bit utilizzata, la "codifica" annotata nella dichiarazione XML deve corrispondere alla codifica effettiva dei byte.
- La codifica a 8 bit verrà convertita in UTF-16 LE dal tipo di dati XML
Tenendo a mente i punti sopra delineati e dato che le stringhe in .NET sono sempre UTF-16 LE / UCS-2 LE (non c'è differenza tra quelle in termini di codifica), possiamo rispondere alle tue domande:
C'è un motivo per cui non dovrei usare StringWriter per serializzare un oggetto quando ne ho bisogno come stringa in seguito?
No, il tuo StringWriter
codice sembra andare bene (almeno non vedo problemi nei miei test limitati usando il 2 ° blocco di codice dalla domanda).
L'impostazione della codifica su UTF-16 (nel tag xml) non funzionerebbe allora?
Non è necessario fornire la dichiarazione XML. Quando manca, si presume che la codifica sia UTF-16 LE se si passa la stringa in SQL Server come NVARCHAR
(ie SqlDbType.NVarChar
) o XML
(ie SqlDbType.Xml
). Si presume che la codifica sia la Code Page predefinita a 8 bit se passata come VARCHAR
(cioè SqlDbType.VarChar
). Se hai caratteri ASCII non standard (cioè valori 128 e superiori) e stai passando come VARCHAR
, allora probabilmente vedrai "?" per i caratteri BMP e "??" per i caratteri supplementari come SQL Server convertirà la stringa UTF-16 da .NET in una stringa a 8 bit della pagina codici del database corrente prima di riconvertirla in UTF-16 / UCS-2. Ma non dovresti ricevere alcun errore.
D'altra parte, se si specifica la dichiarazione XML, è necessario passare a SQL Server utilizzando il tipo di dati corrispondente a 8 bit o 16 bit. Quindi, se hai una dichiarazione che afferma che la codifica è UCS-2 o UTF-16, devi passare come SqlDbType.NVarChar
o SqlDbType.Xml
. Oppure, se si dispone di una dichiarazione attestante che la codifica è una delle opzioni a 8 bit (cioè UTF-8
, Windows-1252
, iso-8859-1
, ecc), allora si deve passare come SqlDbType.VarChar
. La mancata corrispondenza della codifica dichiarata con il tipo di dati SQL Server corretto a 8 o 16 bit comporterà l'errore "impossibile cambiare la codifica" che stavi ricevendo.
Ad esempio, utilizzando il StringWriter
codice di serializzazione basato sul tuo , ho semplicemente stampato la stringa risultante dell'XML e l'ho usata in SSMS. Come puoi vedere di seguito, la dichiarazione XML è inclusa (perché StringWriter
non ha un'opzione per OmitXmlDeclaration
apprezzare XmlWriter
), il che non pone problemi fintanto che passi la stringa come il tipo di dati corretto di SQL Server:
-- Upper-case "N" prefix == NVARCHAR, hence no error:
DECLARE @Xml XML = N'<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
SELECT @Xml;
-- <string>Test ሴ😸</string>
Come puoi vedere, gestisce anche i caratteri oltre lo standard ASCII, dato che ሴ
è BMP Code Point U + 1234, ed 😸
è Supplementary Character Code Point U + 1F638. Tuttavia, quanto segue:
-- No upper-case "N" prefix on the string literal, hence VARCHAR:
DECLARE @Xml XML = '<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
si traduce nel seguente errore:
Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 39, unable to switch the encoding
Ergo, a parte tutte queste spiegazioni, la soluzione completa alla tua domanda originale è:
Stavi chiaramente passando la stringa come SqlDbType.VarChar
. Passa a SqlDbType.NVarChar
e funzionerà senza dover eseguire il passaggio aggiuntivo di rimozione della dichiarazione XML. Ciò è preferibile rispetto a mantenere SqlDbType.VarChar
e rimuovere la dichiarazione XML perché questa soluzione impedirà la perdita di dati quando l'XML include caratteri ASCII non standard. Per esempio:
-- No upper-case "N" prefix on the string literal == VARCHAR, and no XML declaration:
DECLARE @Xml2 XML = '<string>Test ሴ😸</string>';
SELECT @Xml2;
-- <string>Test ???</string>
Come puoi vedere, questa volta non ci sono errori, ma ora c'è una perdita di dati 🙀.