<TL; DR> O problema é bastante simples, na verdade: você não está combinando a codificação declarada (na declaração XML) com o tipo de dados do parâmetro de entrada. Se você adicionou manualmente <?xml version="1.0" encoding="utf-8"?><test/>
à string, declarar SqlParameter
que é do tipo SqlDbType.Xml
ou SqlDbType.NVarChar
geraria o erro "não foi possível mudar a codificação". Então, ao inserir manualmente via T-SQL, uma vez que você mudou a codificação declarada para ser utf-16
, você estava claramente inserindo uma VARCHAR
string (não prefixada com um "N" maiúsculo, portanto, uma codificação de 8 bits, como UTF-8) e não uma NVARCHAR
string (prefixada com um "N" maiúsculo, daí a codificação UTF-16 LE de 16 bits).
A correção deveria ter sido tão simples quanto:
- No primeiro caso, ao adicionar a declaração informando
encoding="utf-8"
: simplesmente não adicione a declaração XML.
- No segundo caso, ao adicionar a declaração informando
encoding="utf-16"
: ou
- simplesmente não adicione a declaração XML, OU
- simplesmente adicione um "N" ao tipo de parâmetro de entrada: em
SqlDbType.NVarChar
vez de SqlDbType.VarChar
:-) (ou, possivelmente, mude para usar SqlDbType.Xml
)
(A resposta detalhada está abaixo)
Todas as respostas aqui são muito complicadas e desnecessárias (independentemente dos 121 e 184 votos positivos para as respostas de Christian e Jon, respectivamente). Eles podem fornecer código funcional, mas nenhum deles realmente responde à pergunta. O problema é que ninguém realmente entendeu a questão, que em última análise é sobre como funciona o tipo de dados XML no SQL Server. Nada contra essas duas pessoas claramente inteligentes, mas essa questão tem pouco ou nada a ver com a serialização para XML. Salvar dados XML no SQL Server é muito mais fácil do que o que está implícito aqui.
Realmente não importa como o XML é produzido, desde que você siga as regras de como criar dados XML no SQL Server. Eu tenho uma explicação mais completa (incluindo código de exemplo de trabalho para ilustrar os pontos descritos abaixo) em uma resposta a esta pergunta: Como resolver o erro “não foi possível mudar a codificação” ao inserir XML no SQL Server , mas o básico é:
- A declaração XML é opcional
- O tipo de dados XML armazena strings sempre como UCS-2 / UTF-16 LE
- Se o seu XML for UCS-2 / UTF-16 LE, então você:
- passe os dados como
NVARCHAR(MAX)
ou XML
/ SqlDbType.NVarChar
(maxsize = -1) ou SqlDbType.Xml
, ou se estiver usando um literal de string, ele deve ser prefixado com um "N" maiúsculo.
- se especificar a declaração XML, deve ser "UCS-2" ou "UTF-16" (nenhuma diferença real aqui)
- Se o seu XML for codificado em 8 bits (por exemplo, "UTF-8" / "iso-8859-1" / "Windows-1252"), você:
- precisa especificar a declaração XML SE a codificação for diferente da página de código especificada pelo agrupamento padrão do banco de dados
- tem de passar na dados como
VARCHAR(MAX)
/ SqlDbType.VarChar
(maxsize = -1), ou se utilizando uma cadeia de caracteres, então ele deve não ser prefixado com uma letra maiúscula "N".
- Qualquer que seja a codificação de 8 bits usada, a "codificação" observada na declaração XML deve corresponder à codificação real dos bytes.
- A codificação de 8 bits será convertida em UTF-16 LE pelo tipo de dados XML
Com os pontos descritos acima em mente, e considerando que as strings em .NET são sempre UTF-16 LE / UCS-2 LE (não há diferença entre elas em termos de codificação), podemos responder às suas perguntas:
Existe uma razão pela qual eu não devo usar StringWriter para serializar um objeto quando eu precisar dele como uma string posteriormente?
Não, seu StringWriter
código parece estar bom (pelo menos não vejo problemas em meu teste limitado usando o segundo bloco de código da pergunta).
Definir a codificação para UTF-16 (na tag xml) não funcionaria então?
Não é necessário fornecer a declaração XML. Quando está ausente, a codificação é considerada UTF-16 LE se você passar a string para o SQL Server como NVARCHAR
(ou seja SqlDbType.NVarChar
) ou XML
( ou seja SqlDbType.Xml
). A codificação é considerada a página de código padrão de 8 bits se for passada como VARCHAR
(ou seja SqlDbType.VarChar
). Se você tiver caracteres ASCII não padrão (ou seja, valores 128 e acima) e estiver transmitindo como VARCHAR
, provavelmente verá "?" para caracteres BMP e "??" para caracteres suplementares, pois o SQL Server converterá a string UTF-16 do .NET em uma string de 8 bits da página de código do banco de dados atual antes de convertê-la novamente em UTF-16 / UCS-2. Mas você não deve receber nenhum erro.
Por outro lado, se você especificar a declaração XML, deverá passar para o SQL Server usando o tipo de dados correspondente de 8 ou 16 bits. Portanto, se você tiver uma declaração afirmando que a codificação é UCS-2 ou UTF-16, deverá passar como SqlDbType.NVarChar
ou SqlDbType.Xml
. Ou, se você tem uma declaração de que a codificação é uma das opções de 8 bits (ou seja UTF-8
, Windows-1252
, iso-8859-1
, etc), então você deve passar em como SqlDbType.VarChar
. A falha em combinar a codificação declarada com o tipo de dados SQL Server de 8 ou 16 bits adequado resultará no erro "não foi possível alternar a codificação" que você estava recebendo.
Por exemplo, usando seu StringWriter
código de serialização baseado em seu , eu simplesmente imprimi a string resultante do XML e a usei no SSMS. Como você pode ver abaixo, a declaração XML está incluída (porque StringWriter
não tem a opção de OmitXmlDeclaration
like XmlWriter
faz), o que não representa nenhum problema, desde que você passe a string como o tipo de dados correto do SQL Server:
-- 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 você pode ver, ele até lida com caracteres além do ASCII padrão, visto que ሴ
é o ponto de código BMP U + 1234 e 😸
é o ponto de código de caractere suplementar U + 1F638. No entanto, o seguinte:
-- No upper-case "N" prefix on the string literal, hence VARCHAR:
DECLARE @Xml XML = '<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
resulta no seguinte erro:
Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 39, unable to switch the encoding
Portanto, toda essa explicação à parte, a solução completa para sua pergunta original é:
Você estava claramente passando a corda como SqlDbType.VarChar
. Alterne para SqlDbType.NVarChar
e ele funcionará sem a necessidade de passar pela etapa extra de remoção da declaração XML. Isso é preferível a manter SqlDbType.VarChar
e remover a declaração XML porque esta solução evitará a perda de dados quando o XML incluir caracteres ASCII não padrão. Por exemplo:
-- 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 você pode ver, não há erro desta vez, mas agora há perda de dados 🙀.