it-swarm.dev

Como resolver o erro "não é possível alternar a codificação" ao inserir XML no SQL Server

Estou tentando inserir na coluna XML (SQL SERVER 2008 R2), mas o servidor está reclamando:

System.Data.SqlClient.SqlException (0x80131904):
Análise XML: linha 1, caractere 39, incapaz de alternar a codificação

Descobri que a coluna XML precisa ser UTF-16 para que a inserção seja bem-sucedida.

O código que estou usando é:

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

Como posso serializar o objeto para estar na string UTF-8?

[~ # ~] edit [~ # ~] : Ok, desculpe a confusão - a string precisa estar em UTF-8. Você estava certo - é UTF-16 por padrão, e se eu tentar inserir no UTF-8, ele passa. Portanto, a questão é como serializar no UTF-8.

Exemplo

Isso causa erros ao tentar inserir no SQL Server:

    <?xml version="1.0" encoding="utf-16"?>
    <MyMessage>Teno</MyMessage>

Isto não:

    <?xml version="1.0" encoding="utf-8"?>
    <MyMessage>Teno</MyMessage>

Atualização

Eu descobri quando o SQL Server 2008 para seu tipo de coluna Xml precisa de utf-8 e quando utf-16 em encoding propriedade da especificação xml que você está tentando inserir:

Quando você deseja adicionar utf-8, adicione parâmetros ao comando SQL como este:

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

Se você tentar adicionar o xmlValueToAdd com encoding=utf-16 na linha anterior, isso produziria erros na inserção. Além disso, o VarChar significa que os caracteres nacionais não são reconhecidos (eles aparecem como pontos de interrogação).

Para adicionar utf-16 ao db, use SqlDbType.NVarChar ou SqlDbType.Xml no exemplo anterior, ou simplesmente não especifique o tipo:

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

Embora uma string .net seja sempre UTF-16 você precisa serializar o objeto usando UTF-16 codificação. Isso deve ser algo como isto:

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

Ao definir a codificação como Encoding.Unicode, não apenas a string será UTF-16 mas você também deve obter a string xml como UTF-16.

<?xml version="1.0" encoding="utf-16"?>
21
Pedro

Esta pergunta é quase duplicada de outras 2 e, surpreendentemente - embora essa seja a mais recente - acredito que esteja faltando a melhor resposta.

As duplicatas, e quais são as melhores respostas, são:

No final, não importa qual codificação é declarada ou usada, desde que o XmlReader possa analisá-la localmente no servidor de aplicativos.

Como foi confirmado em A maneira mais eficiente de ler XML no ADO.net da coluna de tipo XML no SQL server? , o SQL Server armazena XML em um formato binário eficiente. Ao usar a classe SqlXml , o ADO.net pode se comunicar com o SQL Server nesse formato binário e não exigir que o servidor de banco de dados faça serialização ou desserialização de XML. Isso também deve ser mais eficiente para o transporte pela rede.

Ao usar SqlXml, o XML será enviado pré-analisado para o banco de dados e, em seguida, o banco de dados não precisará saber nada sobre codificações de caracteres - UTF-16 ou outro. Em particular, observe que as declarações XML nem mesmo persistem com os dados no banco de dados, independentemente do método usado para inseri-los.

Consulte as respostas vinculadas acima para métodos que se parecem muito com isso, mas este exemplo é meu:

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

Observe que eu não consideraria o último exemplo (não comentado) como "pronto para produção", mas o deixei como está para ser conciso e legível. Se feito corretamente, o StringReader e o XmlReader criado devem ser inicializados nas instruções using para garantir que seus métodos Close() sejam chamados quando concluídos.

Pelo que vi, as declarações XML nunca persistem ao usar uma coluna XML. Mesmo sem usar o .NET e apenas usar essa instrução SQL insert direta, por exemplo, a declaração XML não é salva no banco de dados com o XML:

Insert Into TestData(Xml) Values ('<?xml version="1.0" encoding="UTF-8"?><Test/>');

Agora, em termos da pergunta do OP, o objeto a ser serializado ainda precisa ser convertido em uma estrutura XML a partir do objeto MyMessage e XmlSerializer ainda é necessário para isso. No entanto, na pior das hipóteses, em vez de serializar para uma String, a mensagem pode ser serializada para um XmlDocument - que pode ser passada para SqlXml através de um novo XmlNodeReader - evitando uma viagem de desserialização/serialização para uma string. (Consulte http://blogs.msdn.com/b/jongallant/archive/2007/01/30/how-to-convert-xmldocument-to-xmlreader-for-sqlxml-data-type.aspx para detalhes e um exemplo.)

Tudo aqui foi desenvolvido e testado com o .NET 4.0 e o SQL Server 2008 R2.

Não desperdice executando XML por meio de conversões extras (desserializações e serializações - para DOM, strings ou outros), conforme mostrado em outras respostas aqui e em outros lugares.

34
ziesemer

A solução mais fácil não é dizer ao serializador para não gerar a declaração XML? .NET e SQL devem classificar o resto entre eles.

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

Levei uma eternidade para resolver esse problema.

Eu estava fazendo uma instrução INSERT no SQL Server como algo como:

UPDATE Customers 
SET data = '<?xml version="1.0" encoding="utf-16"?><MyMessage>Teno</MyMessage>';

e isso dá o erro:

Msg 9402, nível 16, estado 1, linha 2
Análise XML: linha 1, caractere 39, incapaz de alternar a codificação

E a correção realmente muito simples é:

UPDATE Customers 
SET data = N'<?xml version="1.0" encoding="utf-16"?><MyMessage>Teno</MyMessage>';

A diferença é prefixar a cadeia Unicode com N:

N '<? xml version = "1.0" encoding = "utf-16"?> Teno </MyMessage>'

No primeiro caso, supõe-se que uma sequência não prefixada seja varchar (por exemplo, página de código do Windows-1252). Quando encontra o encoding="utf-16" dentro da string, existe um conflito (e com razão, já que a string não é utf-16).

A correção é passar a sequência para o SQL server como um nvarchar (ou seja, UTF-16):

N '<? xml version = "1.0" encoding = "utf-16"?>'

Dessa forma, a string is UTF-16, que corresponde à codificação utf-16 que o XML diz que é. O tapete combina com as cortinas, por assim dizer.

7
Ian Boyd

Uma string é sempre UTF-16 no .NET, portanto, enquanto você permanecer dentro do aplicativo gerenciado, não precisará se preocupar com a codificação.

O problema é mais provável quando você fala com o servidor SQL. Sua pergunta não mostra esse código, por isso é difícil apontar o erro exato. Minha sugestão é que você verifique se existe uma propriedade ou atributo que possa ser definido nesse código que especifica a codificação dos dados enviados ao servidor.

5
Isak Savo

resposta do @ ziesemer (acima) é a única resposta totalmente correta para esta pergunta e as duplicatas vinculadas desta pergunta. No entanto, ainda poderia usar um pouco mais de explicação e esclarecimentos. Considere isso como uma extensão da resposta do @ ziesemer.


Mesmo que produzam o resultado desejado, a maioria das respostas a essa pergunta (incluindo a pergunta duplicada) é complicada e passa por várias etapas desnecessárias. O principal problema aqui é a falta geral de entendimento sobre como o tipo de dados XML realmente funciona no SQL Server (não é surpreendente, pois não está bem documentado). O tipo XML:

  1. É um tipo altamente otimizado (para armazenamento) que converte o XML recebido em um formato binário (documentado em algum lugar do site msdn). As otimizações incluem:
    1. Convertendo números e datas de string (como estão no XML) em representações binárias [~ # ~] se [~ # ~] o elemento ou atributo está marcado com as informações de tipo (isso pode exigir a especificação de uma coleção de esquemas XML). Ou seja, o número "1234567" é armazenado como um "int" de 4 bytes em vez de uma string UTF-16 de 14 bytes de 7 dígitos.
    2. Os nomes de elemento e atributo são armazenados em um dicionário e recebem um ID numérico. Esse ID numérico é usado na estrutura em árvore XML. Significado, "<ElementName>...</ElementName> "ocupa 27 caracteres (ou seja, 54 bytes) na forma de sequência, mas apenas 11 caracteres (ou seja, 22 bytes) quando armazenados no tipo XML. E isso é para uma única instância. vários múltiplos adicionais de 54 bytes, mas no tipo XML, cada instância ocupa apenas o espaço desse ID numérico, provavelmente um int de 4 bytes.
  2. Armazena seqüências de caracteres como UTF-16 Little Endian, sempre . É mais provável que a declaração XML não seja armazenada: é totalmente desnecessária, pois é sempre a mesma, pois o atributo "Encoding" nunca pode ser alterado.
  3. Nenhuma declaração XML supõe que a codificação seja UTF-16, não UTF-8.
  4. Pode ter dados de 8 bits/não UTF-16 passados. Nesse caso, você precisa garantir que a sequência de caracteres não seja um NVARCHAR string (ou seja, não é prefixado com um "N" maiúsculo para literais, não declarado como NVARCHAR ao lidar com variáveis ​​T-SQL e não declarado como SqlDbType.NVarChar na rede). E, você precisa se certificar de que possui a declaração XML e especifica a codificação correta.

    PRINT 'VARCHAR / UTF-8:';
    DECLARE @XML_VC_8 XML;
    SET @XML_VC_8 = '<?xml version="1.0" encoding="utf-8"?><test/>';
    PRINT 'Success!'
    -- Success!
    
    GO
    PRINT '';
    PRINT 'NVARCHAR / UTF-8:';
    DECLARE @XML_NVC_8 XML;
    SET @XML_NVC_8 = N'<?xml version="1.0" encoding="utf-8"?><test/>';
    PRINT 'Success!'
    /*
    Msg 9402, Level 16, State 1, Line XXXXX
    XML parsing: line 1, character 38, unable to switch the encoding
    */
    
    GO
    PRINT '';
    PRINT 'VARCHAR / UTF-16:';
    DECLARE @XML_VC_16 XML;
    SET @XML_VC_16 = '<?xml version="1.0" encoding="utf-16"?><test/>';
    PRINT 'Success!'
    /*
    Msg 9402, Level 16, State 1, Line XXXXX
    XML parsing: line 1, character 38, unable to switch the encoding
    */
    
    GO
    PRINT '';
    PRINT 'NVARCHAR / UTF-16:';
    DECLARE @XML_NVC_16 XML;
    SET @XML_NVC_16 = N'<?xml version="1.0" encoding="utf-16"?><test/>';
    PRINT 'Success!'
    -- Success!
    

    Como você pode ver, quando a sequência de entrada é NVARCHAR, a declaração XML pode ser incluída, mas precisa ser "UTF -16 ".

  5. Quando a sequência de entrada é VARCHAR, a declaração XML pode ser incluída, mas não pode seja "UTF-16". No entanto, pode ser qualquer codificação de 8 bits válida; nesse caso, os bytes dessa codificação serão convertidos em UTF-16, conforme mostrado abaixo:

    DECLARE @XML XML;
    SET @XML = '<?xml version="1.0" encoding="utf-8"?><test attr="'
               + CHAR(0xF0) + CHAR(0x9F) + CHAR(0x98) + CHAR(0x8E) + '"/>';
    SELECT @XML;
    -- <test attr="????" />
    
    
    SET @XML = '<?xml version="1.0" encoding="Windows-1255"?><test attr="'
               + CONVERT(VARCHAR(10), 0xF9ECE5ED) + '"/>';
    SELECT @XML AS [XML from Windows-1255],
           CONVERT(VARCHAR(10), 0xF9ECE5ED) AS [Latin1_General / Windows-1252];
    /*
    XML from Windows-1255    Latin1_General / Windows-1252
    <test attr="שלום" />     ùìåí
    */
    

    O primeiro exemplo especifica a sequência UTF-8 de 4 bytes para Rosto sorridente com óculos de sol e é convertida corretamente.
    O segundo exemplo usa 4 bytes para representar 4 letras hebraicas que compõem a palavra "Shalom", que é convertida corretamente e exibida corretamente, pois o byte "F9", que é o primeiro, é o ש caractere, localizado no lado direito da Palavra (já que o hebraico é o idioma da direita para a esquerda). No entanto, esses mesmos 4 bytes são exibidos como ùìåí quando selecionado diretamente, pois o agrupamento padrão para o banco de dados atual é Latin1_General_100_CS_AS_SC.

4
Solomon Rutzky

Você está serializando para uma sequência em vez de uma matriz de bytes, portanto, neste momento, qualquer codificação ainda não aconteceu.

Como é o início de "messageToLog"? O XML está especificando uma codificação (por exemplo, utf-8) que subsequentemente se mostra errada?

Editar

Com base em suas informações adicionais, parece que a string é automaticamente convertida em utf-8 quando é passada para o banco de dados, mas o banco de dados engasga porque o XML declaração diz que é utf-16.

Nesse caso, você não precisa serializar para utf-8. Você precisa serializar com o "encoding =" omitido do XML. O XmlFragmentWriter (não parte padrão do .Net, Google) permite que você faça isso.

1
arx

A codificação padrão para um serializador xml deve ser UTF-16. Apenas para ter certeza de que você pode tentar -

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();
0
Vinay B R