<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 SqlParameterque es de tipo SqlDbType.Xmlo SqlDbType.NVarCharle 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 VARCHARcadena (no con el prefijo "N" en mayúscula, por lo tanto, una codificación de 8 bits, como UTF-8) y no una NVARCHARcadena (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.NVarCharlugar 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 StringWritercó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.NVarCharo 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 StringWritercó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 StringWriterno tiene una opción para Me OmitXmlDeclarationgusta 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.NVarChary funcionará sin necesidad de pasar por el paso adicional de eliminar la declaración XML. Se prefiere a mantener SqlDbType.VarChary 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 🙀.