Как устранить ошибку «невозможно переключить кодировку» при вставке XML в SQL Server - PullRequest
40 голосов
/ 21 сентября 2010

Я пытаюсь вставить в столбец XML (SQL SERVER 2008 R2), но сервер жалуется:

System.Data.SqlClient.SqlException (0x80131904):
Синтаксический анализ XML:строка 1, символ 39, невозможно переключить кодировку

Я обнаружил, что для успешного выполнения вставки столбец XML должен быть UTF-16.

Код IЯ использую это:

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

Как я могу сериализовать объект, чтобы быть в строке UTF-8?

EDIT : Хорошо, извините за смешивание - строкадолжен быть в UTF-8.Вы были правы - это UTF-16 по умолчанию, и если я пытаюсь вставить в UTF-8, он проходит.Поэтому вопрос заключается в том, как выполнить сериализацию в UTF-8.

Пример

Это приводит к ошибкам при попытке вставить в SQL Server:

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

Это не так:

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

Обновление

Я выяснил, когда SQL Server 2008 для его типа столбца Xml требуется utf-8, и когдаutf-16 в encoding свойстве спецификации xml, которую вы пытаетесь вставить:

Если вы хотите добавить utf-8, добавьте параметры в команду SQL следующим образом:

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

Если вы попытаетесь добавить xmlValueToAdd с encoding=utf-16 в предыдущей строке, это приведет к ошибкам при вставке.Кроме того, VarChar означает, что национальные символы не распознаются (они становятся знаками вопроса).

Чтобы добавить utf-16 в БД, используйте SqlDbType.NVarChar или SqlDbType.Xml в предыдущем примере,или просто не указывайте тип:

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

Ответы [ 8 ]

34 голосов
/ 25 января 2012

Этот вопрос является почти дубликатом двух других, и удивительно - хотя этот вопрос является самым последним - я считаю, что ему не хватает лучшего ответа.

Дубликаты и, как я считаю, их лучшие ответы:

В конце концов, не имеет значения, какая кодировка объявлена ​​или используется, если XmlReader может анализировать ее локально на сервере приложений.

Как было подтверждено в Самый эффективный способ чтения XML в ADO.net из столбца типа XML на сервере SQL? , SQL Server хранит XML в эффективном двоичном формате. Используя класс SqlXml, ADO.net может взаимодействовать с SQL Server в этом двоичном формате и не требовать от сервера базы данных какой-либо сериализации или десериализации XML. Это также должно быть более эффективным для транспортировки по сети.

Используя SqlXml, XML будет отправляться предварительно проанализированным в базу данных, а затем БД не нужно ничего знать о кодировках символов - UTF-16 или иным образом. В частности, обратите внимание, что объявления XML даже не сохраняются с данными в базе данных, независимо от того, какой метод используется для их вставки.

Пожалуйста, обратитесь к приведенным выше ответам для методов, которые выглядят очень похоже на это, но этот пример мой:

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

Обратите внимание, что я бы не считал последний (некомментированный) пример "готовым к производству", но оставил его как есть, чтобы быть кратким и читабельным. Если все сделано правильно, и StringReader, и созданный XmlReader должны быть инициализированы в операторах using, чтобы гарантировать, что их методы Close() будут вызваны после завершения.

Из того, что я видел, объявления XML никогда не сохраняются при использовании столбца XML. Например, даже без использования .NET и только с использованием этого прямого оператора вставки SQL объявление XML не сохраняется в базе данных с XML:

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

Теперь, с точки зрения вопроса OP, сериализуемый объект все еще должен быть преобразован в структуру XML из объекта MyMessage, и для этого все еще необходим XmlSerializer. Однако в худшем случае вместо сериализации в строку, сообщение можно вместо этого сериализовать в XmlDocument, который затем может быть передан в SqlXml через новый XmlNodeReader, избегая Сериализация / сериализация поездки в строку. (См. http://blogs.msdn.com/b/jongallant/archive/2007/01/30/how-to-convert-xmldocument-to-xmlreader-for-sqlxml-data-type.aspx для деталей и примера.)

Все здесь было разработано и протестировано с .NET 4.0 и SQL Server 2008 R2.

Пожалуйста, не тратьте впустую , выполняя XML посредством дополнительных преобразований (десериализация и сериализация - в DOM, строки или иным образом), как показано в других ответах здесь и в других местах.

21 голосов
/ 21 сентября 2010

Хотя строка .net всегда UTF-16, вам необходимо сериализовать объект с использованием кодировки UTF-16. Это может быть что-то вроде этого:

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

При установке кодировки на Encoding.Unicode не только строка будет UTF-16, но вы также должны получить строку xml как UTF-16.

<?xml version="1.0" encoding="utf-16"?>
12 голосов
/ 29 января 2013

Разве не самое простое решение сказать сериализатору не выводить декларацию XML? .NET и SQL должны разбираться между ними.

        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();
7 голосов
/ 22 февраля 2016

У меня ушло навсегда, чтобы заново решить эту проблему.

Я делал оператор INSERT в SQL Server как что-то вроде:

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

и это дает ошибку:

Сообщение 9402, Уровень 16, Состояние 1, Строка 2
Синтаксический анализ XML: строка 1, символ 39, невозможно переключить кодировку

И действительно, очень простое решение:

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

Разница заключается в том, что перед строкой Unicode стоит N:

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

В первом случае предполагается, что нефиксированной строкой является varchar (например, кодовая страница Windows-1252). Когда он встречает encoding="utf-16" внутри строки, возникает конфликт (и это справедливо, поскольку строка не utf-16).

Исправление заключается в том, чтобы передать строку на сервер SQL в виде nvarchar (т.е. UTF-16):

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

Таким образом, строка - это UTF-16, которая соответствует кодировке utf-16, которая указана в XML. Ковер соответствует, так сказать, шторам.

5 голосов
/ 21 сентября 2010

Строка всегда UTF-16 в .NET, поэтому, пока вы находитесь внутри управляемого приложения, вам не нужно заботиться о том, какая она кодировка.поговорить с сервером SQL.Ваш вопрос не показывает этот код, поэтому трудно точно определить точную ошибку.Я предлагаю вам проверить, есть ли свойство или атрибут, который вы можете установить в этом коде, который задает кодировку данных, отправляемых на сервер.

4 голосов
/ 04 декабря 2018

@ ziesemer's answer (выше) - единственный полностью правильный ответ на этот вопрос и связанные с ним дубликаты этого вопроса.Тем не менее, он все еще может использовать немного больше объяснений и некоторых разъяснений.Рассматривайте это как продолжение ответа @ ziesemer.


Даже если они дают желаемый результат, большинство ответов на этот вопрос (включая дублирующий вопрос) являются сложными и проходят через множество ненужных шагов.Основной проблемой здесь является полное отсутствие понимания того, как тип данных XML на самом деле работает в SQL Server (неудивительно, учитывая, что он плохо документирован).Тип XML:

  1. Это высоко оптимизированный (для хранения) тип, который преобразует входящий XML в двоичный формат (который задокументирован где-то на сайте msdn).Оптимизация включает:
    1. Преобразование чисел и дат из строки (как они есть в XML) в двоичные представления IF элемент или атрибут помечен с помощью информации о типе (для этого может потребоваться указатьКоллекция XML-схем).Это означает, что число «1234567» сохраняется как 4-байтовое «int» вместо 14-байтовой строки UTF-16 из 7 цифр.
    2. Имена элементов и атрибутов хранятся в словаре и получают числовойЯ БЫ.Этот числовой идентификатор используется в древовидной структуре XML.Это означает, что «<ElementName>...</ElementName>» занимает 27 символов (т.е. 54 байта) в строковой форме, но только 11 символов (то есть 22 байта) при сохранении в типе XML.И это только для одного случая.Несколько экземпляров занимают дополнительные кратные 54 байта.Но в типе XML каждый экземпляр занимает только пространство этого числового идентификатора, скорее всего, 4-байтовое целое число.
  2. Хранит строки как UTF-16 Little Endian, всегда .Скорее всего, именно поэтому объявление XML не сохраняется: оно совершенно не нужно, поскольку оно всегда одинаково, поскольку атрибут «Кодировка» никогда не может измениться.
  3. Ни одно объявление XML не предполагает кодировку UTF-16, не UTF-8.
  4. Может передаваться 8-битные / не-UTF-16 данные. В этом случае вам нужно убедиться, что строка не строка NVARCHAR (т.е. не имеет префикс «N» в верхнем регистре для литералов, не объявляется как NVARCHAR при работе с переменными T-SQL и не объявляется как SqlDbType.NVarChar в .NET),И, вам нужно убедиться, что у вас do есть объявление XML, и что оно указывает правильную кодировку.

    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!
    

    Как вы можете видеть, когда строка вводаNVARCHAR, тогда может быть включено объявление XML , но оно должно быть "UTF-16".

  5. Когда входная строка равна VARCHAR, тогдаобъявление XML может быть включено , но оно не может быть "UTF-16".Однако это может быть любое допустимое 8-битное кодирование, и в этом случае байты для этого кодирования будут преобразованы в UTF-16, как показано ниже:

    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="שלום" />     ùìåí
    */
    

    В первом примере указывается 4-байтовый кодПоследовательность UTF-8 для Улыбающееся лицо с солнцезащитными очками и она правильно преобразована.
    Во втором примере используются 4 байта для представления 4 букв иврита, составляющих слово «шалом», которое правильно преобразовано и отображаетсяправильно, учитывая, что байт "F9", который является первым, является символом ש, который находится справа от слова (поскольку иврит является языком справа налево).Тем не менее, те же самые 4 байта отображаются как ùìåí при прямом выборе, так как параметры сортировки по умолчанию для текущей БД Latin1_General_100_CS_AS_SC.

1 голос
/ 21 сентября 2010

Вы сериализуетесь в строку, а не в байтовый массив, поэтому на данный момент никакой кодировки еще не произошло.

Как выглядит начало "messageToLog"?Указывает ли XML кодировку (например, utf-8), которая впоследствии оказывается неправильной?

Редактировать

Исходя из вашей дополнительной информации, звучит как строка автоматически преобразуется в utf-8 при передаче в базу данных, но база данных задыхается, потому что объявление XML говорит, что это utf-16.

В этом случае вы не't нужно сериализовать в utf-8.Вам нужно сериализовать с "encoding =", опущенным в XML.XmlFragmentWriter (не является стандартной частью .Net, Google это) позволяет вам сделать это.

0 голосов
/ 21 сентября 2010

Кодировка по умолчанию для сериализатора xml должна быть UTF-16. Просто чтобы убедиться, что вы можете попробовать -

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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...