Come risolvere l’errore “imansible cambiare la codifica” quando si inserisce XML in SQL Server

Sto cercando di inserire nella colonna XML (SQL SERVER 2008 R2), ma lamentarsi del server:

System.Data.SqlClient.SqlException (0x80131904):
Analisi XML: riga 1, carattere 39, incapace di cambiare la codifica

Ho scoperto che la colonna XML deve essere UTF-16 affinché l’inserimento abbia successo.

Il codice che sto usando è:

XmlSerializer serializer = new XmlSerializer(typeof(MyMessage)); StringWriter str = new StringWriter(); serializer.Serialize(str, message); string messageToLog = str.ToString(); 

Come posso serializzare l’object nella stringa UTF-8?

EDIT : Ok, mi dispiace per il mixup – la stringa deve essere in UTF-8. Avevi ragione: è l’UTF-16 di default, e se provo a inserire in UTF-8 passa. Quindi la domanda è come serializzare in UTF-8.

Esempio

Ciò causa errori durante il tentativo di inserimento in SQL Server:

   Teno 

Questo non:

   Teno 

Aggiornare

Ho capito quando SQL Server 2008 per il suo tipo di colonna Xml bisogno di utf-8, e quando utf-16 nella proprietà di encoding della specifica xml che stai cercando di inserire:

Quando vuoi aggiungere utf-8 , aggiungi i parametri al comando SQL in questo modo:

  sqlcmd.Parameters.Add("ParamName", SqlDbType.VarChar).Value = xmlValueToAdd; 

Se provi ad aggiungere xmlValueToAdd con encoding=utf-16 nella riga precedente, produrrebbe errori in insert. Inoltre, VarChar significa che i caratteri nazionali non sono riconosciuti (si rivelano come punti interrogativi).

Per aggiungere utf-16 a db, utilizzare SqlDbType.NVarChar o SqlDbType.Xml nell’esempio precedente, o semplicemente non specificare affatto il tipo:

  sqlcmd.Parameters.Add(new SqlParameter("ParamName", xmlValueToAdd)); 

Sebbene una stringa .net sia sempre UTF-16 è necessario serializzare l’object usando la codifica UTF-16 . Potrebbe essere qualcosa del genere:

 public static string ToString(object source, Type type, Encoding encoding) { // The string to hold the object content String content; // Create a memoryStream into which the data can be written and readed using (var stream = new MemoryStream()) { // Create the xml serializer, the serializer needs to know the type // of the object that will be serialized var xmlSerializer = new XmlSerializer(type); // Create a XmlTextWriter to write the xml object source, we are going // to define the encoding in the constructor using (var writer = new XmlTextWriter(stream, encoding)) { // Save the state of the object into the stream xmlSerializer.Serialize(writer, source); // Flush the stream writer.Flush(); // Read the stream into a string using (var reader = new StreamReader(stream, encoding)) { // Set the stream position to the begin stream.Position = 0; // Read the stream into a string content = reader.ReadToEnd(); } } } // Return the xml string with the object content return content; } 

Impostando la codifica su Encoding.Unicode, non solo la stringa sarà UTF-16 ma dovresti anche ottenere la stringa xml come UTF-16 .

  

Questa domanda è quasi un duplicato di altri due, e sorprendentemente – mentre questa è la più recente – credo che manchi la migliore risposta.

I duplicati e quelle che credo siano le loro migliori risposte sono:

Alla fine, non importa quale codifica viene dichiarata o utilizzata, purché XmlReader possa analizzarla localmente all’interno del server delle applicazioni.

Come è stato confermato in modo più efficiente per leggere XML in ADO.net dalla colonna di tipo XML nel server SQL? , SQL Server archivia XML in un formato binario efficiente. Utilizzando la class SqlXml , ADO.net può comunicare con SQL Server in questo formato binario e non richiedere al server del database di eseguire serializzazione o de-serializzazione di XML. Questo dovrebbe anche essere più efficiente per il trasporto attraverso la rete.

Usando SqlXml , XML verrà inviato pre-analizzato al database, e quindi il DB non ha bisogno di sapere nulla sulle codifiche dei caratteri – UTF-16 o altro. In particolare, si noti che le dichiarazioni XML non sono nemmeno persistenti con i dati nel database, indipendentemente dal metodo utilizzato per inserirlo.

Si prega di fare riferimento alle risposte sopra collegate per metodi che sembrano molto simili a questo, ma questo esempio è mio:

 using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using System.IO; using System.Xml; static class XmlDemo { static void Main(string[] args) { using(SqlConnection conn = new SqlConnection()) { conn.ConnectionString = "..."; conn.Open(); using(SqlCommand cmd = new SqlCommand("Insert Into TestData(Xml) Values (@Xml)", conn)) { cmd.Parameters.Add(new SqlParameter("@Xml", SqlDbType.Xml) { // Works. // Value = "" // Works. XML Declaration is not persisted! // Value = "" // Works. XML Declaration is not persisted! // Value = "" // Error ("unable to switch the encoding" SqlException). // Value = "" // Works. XML Declaration is not persisted! Value = new SqlXml(XmlReader.Create(new StringReader(""))) }); cmd.ExecuteNonQuery(); } } } } 

Si noti che non considererei l’ultimo esempio (non commentato) come “pronto per la produzione”, ma l’ho lasciato così com’è per essere conciso e leggibile. Se eseguito correttamente, sia StringReader che XmlReader creati devono essere inizializzati all’interno delle istruzioni per garantire che i loro metodi Close() vengano richiamati al termine.

Da quello che ho visto, le dichiarazioni XML non vengono mai mantenute quando si utilizza una colonna XML. Anche senza l’utilizzo di .NET e l’utilizzo di questa istruzione di inserimento SQL diretta, ad esempio, la dichiarazione XML non viene salvata nel database con XML:

 Insert Into TestData(Xml) Values (''); 

Ora, in termini di domanda dell’OP, l’object da serializzare deve ancora essere convertito in una struttura XML dall’object MyMessage e XmlSerializer è ancora necessario per questo. Tuttavia, nella peggiore delle ipotesi, anziché serializzare su una stringa, il messaggio potrebbe invece essere serializzato su un XmlDocument , che può quindi essere passato a SqlXml tramite un nuovo XmlNodeReader , evitando una XmlNodeReader / serializzazione in una stringa. (Vedi http://blogs.msdn.com/b/jongallant/archive/2007/01/30/how-to-convert-xmldocument-to-xmlreader-for-sqlxml-data-type.aspx per dettagli e un esempio .)

Tutto qui è stato sviluppato e testato con .NET 4.0 e SQL Server 2008 R2.

Si prega di non sprecare eseguendo XML attraverso conversioni extra (deserializzazioni e serializzazioni – a DOM, stringhe o altro), come mostrato in altre risposte qui e altrove.

Non è la soluzione più semplice per dire al serializzatore di non uscire la dichiarazione XML? .NET e SQL dovrebbero ordinare il resto tra di loro.

  XmlSerializer serializer = new XmlSerializer(typeof(MyMessage)); StringWriter str = new StringWriter(); using (XmlWriter writer = XmlWriter.Create(str, new XmlWriterSettings { OmitXmlDeclaration = true })) { serializer.Serialize(writer, message); } string messageToLog = str.ToString(); 

Mi ci è voluto un tempo per risolvere di nuovo questo problema.

Stavo facendo una dichiarazione INSERT in SQL Server come qualcosa del tipo:

 UPDATE Customers SET data = 'Teno'; 

e questo dà l’errore:

Messaggio 9402, livello 16, stato 1, riga 2
Analisi XML: riga 1, carattere 39, incapace di cambiare la codifica

E la soluzione davvero molto semplice è:

 UPDATE Customers SET data = N'Teno'; 

La differenza è il prefisso della stringa Unicode con N :

N Teno

Nel primo caso si presume che una stringa non prefissata sia varchar (ad es. Code page Windows-1252). Quando incontra la encoding="utf-16" all’interno della stringa, c’è un conflitto (e giustamente, poiché la stringa non è utf-16).

La correzione è passare la stringa al server SQL come un nvarchar (cioè UTF-16):

N

In questo modo la stringa è UTF-16, che corrisponde alla codifica utf-16 che l’XML dice di essere. Il tappeto abbina le tende, per così dire.

Una stringa è sempre UTF-16 in .NET, quindi finché rimani all’interno della tua app gestita non devi preoccuparti di quale sia la codifica.

Il problema è più probabile quando si parla al server SQL. La tua domanda non mostra quel codice, quindi è difficile individuare l’errore esatto. Il mio suggerimento è di verificare se c’è una proprietà o un attributo che è ansible impostare su quel codice che specifica la codifica dei dati inviati al server.

Si sta serializzando su una stringa piuttosto che su un array di byte, quindi, a questo punto, nessuna codifica non è ancora avvenuta.

Come si presenta l’inizio di “messageToLog”? L’XML specifica una codifica (ad es. Utf-8) che successivamente risulta errata?

modificare

Sulla base delle tue ulteriori informazioni sembra che la stringa sia automaticamente convertita in utf-8 quando viene passata al database, ma il database soffoca perché la dichiarazione XML dice che è utf-16.

In tal caso, non è necessario serializzare su utf-8. È necessario serializzare con “encoding =” omesso dall’XML. XmlFragmentWriter (non una parte standard di .Net, Google it) ti consente di farlo.

La codifica predefinita per un serializzatore xml deve essere UTF-16. Solo per essere sicuro di poter provare –

 XmlSerializer serializer = new XmlSerializer(typeof(YourObject)); // create a MemoryStream here, we are just working // exclusively in memory System.IO.Stream stream = new System.IO.MemoryStream(); // The XmlTextWriter takes a stream and encoding // as one of its constructors System.Xml.XmlTextWriter xtWriter = new System.Xml.XmlTextWriter(stream, Encoding.UTF16); serializer.Serialize(xtWriter, yourObjectInstance); xtWriter.Flush();