Cómo resolver el error “no se puede cambiar la encoding” al insertar XML en SQL Server

Estoy tratando de insertar en la columna XML (SQL Server 2008 R2), pero el servidor se queja:

System.Data.SqlClient.SqlException (0x80131904):
Análisis XML: línea 1, carácter 39, no se puede cambiar la encoding

Descubrí que la columna XML debe ser UTF-16 para que la inserción sea exitosa.

El código que estoy usando es:

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

¿Cómo puedo serializar el objeto para que esté en la cadena UTF-8?

EDITAR : Ok, lo siento por la confusión – la cadena debe estar en UTF-8. Tenías razón: es UTF-16 por defecto, y si bash insertarlo en UTF-8, pasa. Entonces la pregunta es cómo serializar en UTF-8.

Ejemplo

Esto causa errores al intentar insertar en SQL Server:

   Teno 

Esto no:

   Teno 

Actualizar

Me di cuenta cuando el SQL Server 2008 para su tipo de columna Xml necesita utf-8, y cuando utf-16 en la propiedad de encoding de la especificación xml que está tratando de insertar:

Cuando desee agregar utf-8 , agregue parámetros al comando SQL de esta manera:

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

Si intenta agregar xmlValueToAdd con encoding=utf-16 en la fila anterior, produciría errores en insert. Además, VarChar significa que los caracteres nacionales no son reconocidos (se convierten en signos de interrogación).

Para agregar utf-16 a db, utilice SqlDbType.NVarChar o SqlDbType.Xml en el ejemplo anterior, o simplemente no especifique el tipo:

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

Aunque una cadena de .NET siempre es UTF-16 , necesita serializar el objeto utilizando la UTF-16 . Eso debería ser algo como esto:

 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; } 

Al establecer la encoding en Encoding.Unicode, no solo la cadena será UTF-16 sino que también debería obtener la cadena xml como UTF-16 .

  

Esta pregunta es casi duplicada de otras dos, y sorprendentemente, aunque esta es la más reciente, creo que falta la mejor respuesta.

Los duplicados, y lo que creo que son sus mejores respuestas, son:

Al final, no importa qué encoding se declare o utilice, siempre que XmlReader pueda analizarlo localmente dentro del servidor de la aplicación.

Como se confirmó en la forma más eficiente para leer XML en ADO.net desde la columna de tipo XML en el servidor SQL? , SQL Server almacena XML en un formato binario eficiente. Al usar la clase SqlXml , ADO.net puede comunicarse con SQL Server en este formato binario, y no requiere que el servidor de base de datos realice ninguna serialización o deserialización de XML. Esto también debería ser más eficiente para el transporte a través de la red.

Al usar SqlXml , XML se enviará previamente analizado a la base de datos, y luego el DB no necesita saber nada acerca de las codificaciones de caracteres – UTF-16 u otras. En particular, tenga en cuenta que las declaraciones XML ni siquiera se conservan con los datos en la base de datos, independientemente del método que se utilice para insertarlo.

Por favor refiérase a las respuestas arriba relacionadas para métodos que se ven muy similares a esto, pero este ejemplo es mío:

 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(); } } } } 

Tenga en cuenta que no consideraría el último ejemplo (no comentado) como “listo para producción”, pero lo dejé tal como está para ser conciso y legible. Si se hace correctamente, tanto el StringReader como el XmlReader creado deben inicializarse dentro de sentencias using para garantizar que se XmlReader sus métodos Close() cuando se completen.

Por lo que he visto, las declaraciones XML nunca se conservan cuando se utiliza una columna XML. Incluso sin usar .NET y simplemente utilizando esta instrucción de inserción SQL directa, por ejemplo, la statement XML no se guarda en la base de datos con el XML:

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

Ahora, en términos de la pregunta del OP, el objeto a ser serializado todavía necesita ser convertido en una estructura XML del objeto MyMessage , y XmlSerializer todavía es necesario para esto. Sin embargo, en el peor de los casos, en lugar de serializar en una Cadena, el mensaje podría serializarse en un XmlDocument , que luego se puede pasar a SqlXml través de un nuevo XmlNodeReader , evitando un viaje de serialización / serialización a una cadena. (Consulte http://blogs.msdn.com/b/jongallant/archive/2007/01/30/how-to-convert-xmldocument-to-xmlreader-for-sqlxml-data-type.aspx para obtener detalles y un ejemplo) .)

Todo aquí fue desarrollado y probado con .NET 4.0 y SQL Server 2008 R2.

No desperdicie ejecutando XML a través de conversiones adicionales (deserializaciones y serializaciones, a DOM, cadenas u otros), como se muestra en otras respuestas aquí y en otros lugares.

¿No es la solución más fácil decirle al serializador que no muestre la statement XML? .NET y SQL deberían ordenar el rest entre ellos.

  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(); 

Me tomó una eternidad volver a resolver este problema.

Estaba haciendo una INSERT en SQL Server como algo así como:

 UPDATE Customers SET data = 'Teno'; 

y esto da el error:

Msg 9402, nivel 16, estado 1, línea 2
Análisis XML: línea 1, carácter 39, no se puede cambiar la encoding

Y la solución realmente, muy simple es:

 UPDATE Customers SET data = N'Teno'; 

La diferencia es prefijando la cadena Unicode con N :

N Teno

En el primer caso, se supone que una cadena no prefijada es varchar (por ejemplo, la página de códigos de Windows-1252). Cuando encuentra la encoding="utf-16" dentro de la cadena, hay un conflicto (y con razón, ya que la cadena no es utf-16).

La solución es pasar la cadena al servidor SQL como un nvarchar (es decir, UTF-16):

N

De esa forma, la cadena es UTF-16, que coincide con la encoding utf-16 que el XML dice que es. La alfombra coincide con las cortinas, por así decirlo.

Una cadena siempre es UTF-16 en .NET, por lo que mientras permanezca dentro de su aplicación administrada no tiene que preocuparse de qué encoding es.

El problema es más probable cuando habla con el servidor SQL. Su pregunta no muestra ese código, por lo que es difícil precisar el error exacto. Mi sugerencia es que compruebe si hay una propiedad o atributo que puede establecer en ese código que especifica la encoding de los datos enviados al servidor.

Está serializando en una cadena en lugar de una matriz de bytes, por lo que, en este momento, aún no se ha producido ninguna encoding.

¿Cómo se ve el comienzo de “messageToLog”? ¿El XML especifica una encoding (por ejemplo, utf-8) que posteriormente resulta ser incorrecta?

Editar

Según su información adicional, parece que la cadena se convierte automáticamente a utf-8 cuando se pasa a la base de datos, pero la base de datos se bloquea porque la statement XML dice que es utf-16.

En ese caso, no necesita serializar a utf-8. Debe serializar con la “encoding =” omitida del XML. El XmlFragmentWriter (no una parte estándar de .Net, Google it) le permite hacer esto.

La encoding predeterminada para un serializador xml debe ser UTF-16. Solo para asegurarte de que puedes intentar …

 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();