serialize - stringwriter encoding utf-8 c#
Cómo resolver el error "no se puede cambiar la codificación" al insertar XML en SQL Server (7)
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 codificación
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 intento 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:
<?xml version="1.0" encoding="utf-16"?>
<MyMessage>Teno</MyMessage>
Esto no:
<?xml version="1.0" encoding="utf-8"?>
<MyMessage>Teno</MyMessage>
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));
¿No es la solución más fácil decirle al serializador que no muestre la declaración XML? .NET y SQL deberían ordenar el resto 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();
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 codificación en Encoding.Unicode, no solo la cadena será UTF-16
sino que también debería obtener la cadena xml como UTF-16
.
<?xml version="1.0" encoding="utf-16"?>
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 codificación.
¿Cómo se ve el comienzo de "messageToLog"? ¿El XML especifica una codificación (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 declaración XML dice que es utf-16.
En ese caso, no necesita serializar a utf-8. Debe serializar con la "codificación =" omitida del XML. El XmlFragmentWriter (no una parte estándar de .Net, Google it) le permite hacer esto.
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:
- Uso de StringWriter para la serialización de XML (2009-10-14)
- https://.com/a/1566154/751158
- Se produce un error al tratar de almacenar contenido XML en SQL Server 2005 (problema de codificación) (2008-12-21)
- https://.com/a/1091209/751158
Al final, no importa qué codificación 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 = "<Test/>"
// Works. XML Declaration is not persisted!
// Value = "<?xml version=/"1.0/"?><Test/>"
// Works. XML Declaration is not persisted!
// Value = "<?xml version=/"1.0/" encoding=/"UTF-16/"?><Test/>"
// Error ("unable to switch the encoding" SqlException).
// Value = "<?xml version=/"1.0/" encoding=/"UTF-8/"?><Test/>"
// Works. XML Declaration is not persisted!
Value = new SqlXml(XmlReader.Create(new StringReader("<?xml version=/"1.0/" encoding=/"UTF-8/"?><Test/>")))
});
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 declaración XML no se guarda en la base de datos con el XML:
Insert Into TestData(Xml) Values (''<?xml version="1.0" encoding="UTF-8"?><Test/>'');
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.
La codificación 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();
Me tomó una eternidad volver a resolver este problema.
Estaba haciendo una INSERT
en SQL Server como algo así como:
UPDATE Customers
SET data = ''<?xml version="1.0" encoding="utf-16"?><MyMessage>Teno</MyMessage>'';
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 codificación
Y la solución realmente, muy simple es:
UPDATE Customers
SET data = N''<?xml version="1.0" encoding="utf-16"?><MyMessage>Teno</MyMessage>'';
La diferencia es prefijando la cadena Unicode con N
:
N ''<? Xml version = "1.0" encoding = "utf-16"?> Teno </ MyMessage>''
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 ''<? Xml version = "1.0" encoding = "utf-16"?>''
De esa forma, la cadena es UTF-16, que coincide con la codificación 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é codificación 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 codificación de los datos enviados al servidor.