Как решить проблему "неспособность переключить кодировку" при вставке XML в SQL Server

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

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

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

Используемый мной код:

 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 в db, используйте либо SqlDbType.NVarChar или SqlDbType.Xml в предыдущем примере, либо просто не указывайте тип вообще:

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

Ответ 1

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

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"?>

Ответ 2

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

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

В конце концов, не имеет значения, какая кодировка объявлена ​​или используется, если 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. Однако в худшем случае вместо сериализации в String сообщение может быть сериализовано в 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, строки или иначе), как показано в других ответах здесь и в других местах.

Ответ 3

Не самое простое решение сказать, что сериализатор не должен выполнять декларацию 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();

Ответ 4

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

Я делал инструкцию INSERT в SQL Server как-то вроде:

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

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

Msg 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 </MyMessage> '

В первом случае считается, что строка без префиксов является 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

Строка всегда является UTF-16 в .NET, поэтому, пока вы остаетесь в управляемом приложении, вам не нужно заботиться о том, какая именно кодировка.

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

Ответ 6

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


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

  1. Является высоко оптимизированным (для хранения) типом, который преобразует входящий XML в двоичный формат (который зарегистрирован где-то на сайте msdn). Оптимизация включает:
    1. Преобразование чисел и дат из строки (как они есть в XML) в двоичные представления. Если элемент или атрибут помечен информацией типа (для этого может потребоваться указать сборку XML-схемы). Значение "1234567" хранится как 4-байтовый "int" вместо 14-байтовой строки UTF-16 из 7 цифр.
    2. Имена элементов и атрибутов хранятся в словаре и задаются числовым идентификатором. Этот числовой идентификатор используется в структуре дерева XML. Значение " <ElementName>...</ElementName> " занимает 27 символов (т.е. 54 байта) в строковой форме, но только 11 символов (т.е. 22 байта) при сохранении в типе XML. И это для одного его экземпляра. Несколько экземпляров занимают дополнительные кратные 54 байта. Но в типе XML каждый экземпляр занимает только пространство этого числового идентификатора, скорее всего это 4-байтовый int.
  2. Сохраняет строки как UTF-16 Little Endian, всегда. Скорее всего, поэтому XML-декларация не сохраняется: она совершенно не нужна, поскольку она всегда одна и та же, поскольку атрибут "Кодирование" не может измениться.
  3. Никакая декларация XML не предполагает кодирование UTF-16, а не UTF-8.
  4. Может иметь 8-битные/не-UTF-16 данные. В этом случае вам нужно убедиться, что строка не является строкой NVARCHAR (т. NVARCHAR Не префикс с верхним регистром "N" для литералов, а не объявлен как NVARCHAR при работе с переменными T-SQL и не объявлен как SqlDbType.NVarChar в.NET). И, вам нужно убедиться, что у вас есть объявление 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 для Smiling Face with Sunglasses и она преобразуется правильно.
    Второй пример использует 4 байта для представления 4 букв на иврите, составляющих слово "Шалом", которое преобразуется правильно и отображается правильно, учитывая, что байт "F9", который является первым, является символом ש который находится справа, (поскольку иврит - это язык справа налево). Тем не менее те же 4 байта отображаются как ùìåí при ùìåí непосредственно, поскольку значение по умолчанию для текущей базы Latin1_General_100_CS_AS_SC по умолчанию - Latin1_General_100_CS_AS_SC.

Ответ 7

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

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

Edit

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

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

Ответ 8

Кодировка по умолчанию для 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();