<TL; DR> El problema es bastante simple, en realidad: no está haciendo coincidir la codificación declarada (en la declaración XML) con el tipo de datos del parámetro de entrada. Si agregó manualmente <?xml version="1.0" encoding="utf-8"?><test/>
a la cadena, entonces declarar SqlParameter
que es de tipo SqlDbType.Xml
o SqlDbType.NVarChar
le daría el error "No se puede cambiar la codificación". Luego, al insertar manualmente a través de T-SQL, ya que cambió la codificación declarada para serutf-16
, claramente estaba insertando una VARCHAR
cadena (no con el prefijo "N" en mayúscula, por lo tanto, una codificación de 8 bits, como UTF-8) y no una NVARCHAR
cadena (con el prefijo "N" en mayúscula, de ahí la codificación UTF-16 LE de 16 bits).
La solución debería haber sido tan simple como:
- En el primer caso, al agregar la declaración que dice
encoding="utf-8"
: simplemente no agregue la declaración XML.
- En el segundo caso, al agregar la declaración que indica
encoding="utf-16"
: o bien
- simplemente no agregue la declaración XML, O
- simplemente agregue una "N" al tipo de parámetro de entrada: en
SqlDbType.NVarChar
lugar de SqlDbType.VarChar
:-) (o posiblemente incluso cambie a usar SqlDbType.Xml
)
(La respuesta detallada está a continuación)
Todas las respuestas aquí son demasiado complicadas e innecesarias (independientemente de los 121 y 184 votos a favor de las respuestas de Christian y Jon, respectivamente). Es posible que proporcionen un código de trabajo, pero ninguno de ellos realmente responde a la pregunta. El problema es que nadie entendió realmente la pregunta, que en última instancia se trata de cómo funciona el tipo de datos XML en SQL Server. Nada en contra de esas dos personas claramente inteligentes, pero esta pregunta tiene poco o nada que ver con la serialización en XML. Guardar datos XML en SQL Server es mucho más fácil de lo que se implica aquí.
Realmente no importa cómo se produce el XML siempre que siga las reglas de cómo crear datos XML en SQL Server. Tengo una explicación más completa (incluido un código de ejemplo de trabajo para ilustrar los puntos que se describen a continuación) en una respuesta a esta pregunta: Cómo resolver el error "no se puede cambiar la codificación" al insertar XML en SQL Server , pero los conceptos básicos son:
- La declaración XML es opcional
- El tipo de datos XML almacena cadenas siempre como UCS-2 / UTF-16 LE
- Si su XML es UCS-2 / UTF-16 LE, entonces:
- pasar los datos como
NVARCHAR(MAX)
o XML
/ SqlDbType.NVarChar
(maxsize = -1) o SqlDbType.Xml
, o si usa una cadena literal, debe tener el prefijo "N" en mayúscula.
- si especifica la declaración XML, debe ser "UCS-2" o "UTF-16" (no hay diferencia real aquí)
- Si su XML está codificado en 8 bits (por ejemplo, "UTF-8" / "iso-8859-1" / "Windows-1252"), entonces:
- es necesario especificar la declaración XML SI la codificación es diferente a la página de códigos especificada por la intercalación predeterminada de la base de datos
- debe pasar en los datos mientras que
VARCHAR(MAX)
/ SqlDbType.VarChar
(maxsize = -1), o si se utiliza una cadena literal entonces debe no ser prefijado con una mayúscula "N".
- Cualquiera que sea la codificación de 8 bits que se utilice, la "codificación" indicada en la declaración XML debe coincidir con la codificación real de los bytes.
- La codificación de 8 bits se convertirá en UTF-16 LE por el tipo de datos XML
Teniendo en cuenta los puntos descritos anteriormente, y dado que las cadenas en .NET siempre son UTF-16 LE / UCS-2 LE (no hay diferencia entre ellas en términos de codificación), podemos responder a sus preguntas:
¿Hay alguna razón por la que no debería usar StringWriter para serializar un objeto cuando lo necesito como una cadena después?
No, su StringWriter
código parece estar bien (al menos no veo problemas en mis pruebas limitadas usando el segundo bloque de código de la pregunta).
Entonces, ¿no funcionaría configurar la codificación en UTF-16 (en la etiqueta xml)?
No es necesario proporcionar la declaración XML. Cuando falta, se asume que la codificación es UTF-16 LE si pasa la cadena a SQL Server como NVARCHAR
(ie SqlDbType.NVarChar
) o XML
(ie SqlDbType.Xml
). Se supone que la codificación es la página de códigos de 8 bits predeterminada si se pasa como VARCHAR
(es decir SqlDbType.VarChar
). Si tiene caracteres ASCII no estándar (es decir, valores 128 y superiores) y los está pasando comoVARCHAR
, es probable que vea "?" para caracteres BMP y "??" para caracteres suplementarios, ya que SQL Server convertirá la cadena UTF-16 de .NET en una cadena de 8 bits de la página de códigos de la base de datos actual antes de volver a convertirla en UTF-16 / UCS-2. Pero no debería recibir ningún error.
Por otro lado, si especifica la declaración XML, debe pasar a SQL Server utilizando el tipo de datos de 8 o 16 bits correspondiente. Entonces, si tiene una declaración que indica que la codificación es UCS-2 o UTF-16, debe pasar como SqlDbType.NVarChar
o SqlDbType.Xml
. O, si usted tiene una declaración de que la codificación es una de las opciones de 8 bits (es decir UTF-8
, Windows-1252
, iso-8859-1
, etc), entonces usted debe pasar en tan SqlDbType.VarChar
. Si no coincide la codificación declarada con el tipo de datos de SQL Server de 8 o 16 bits adecuado, se producirá el error "No se puede cambiar la codificación" que estaba recibiendo.
Por ejemplo, usando su StringWriter
código de serialización basado en, simplemente imprimí la cadena resultante del XML y la usé en SSMS. Como puede ver a continuación, se incluye la declaración XML (porque StringWriter
no tiene una opción para Me OmitXmlDeclaration
gusta XmlWriter
), lo que no plantea ningún problema siempre que pase la cadena como el tipo de datos SQL Server correcto:
-- 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>
Como puede ver, incluso maneja caracteres más allá de ASCII estándar, dado que ሴ
es el punto de código BMP U + 1234, y 😸
es el punto de código de carácter suplementario U + 1F638. Sin embargo, lo siguiente:
-- No upper-case "N" prefix on the string literal, hence VARCHAR:
DECLARE @Xml XML = '<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
da como resultado el siguiente error:
Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 39, unable to switch the encoding
Ergo, dejando de lado toda esa explicación, la solución completa a su pregunta original es:
Claramente estabas pasando la cuerda como SqlDbType.VarChar
. Cambie a SqlDbType.NVarChar
y funcionará sin necesidad de pasar por el paso adicional de eliminar la declaración XML. Se prefiere a mantener SqlDbType.VarChar
y eliminar la declaración XML porque esta solución evitará la pérdida de datos cuando el XML incluye caracteres ASCII no estándar. Por ejemplo:
-- 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>
Como puede ver, esta vez no hay ningún error, pero ahora hay pérdida de datos 🙀.