Использование StringWriter для сериализации XML

В настоящее время я ищу простой способ сериализации объектов (в С# 3).

Я просмотрел несколько примеров и придумал что-то вроде:

MemoryStream memoryStream = new MemoryStream ( );
XmlSerializer xs = new XmlSerializer ( typeof ( MyObject) );
XmlTextWriter xmlTextWriter = new XmlTextWriter ( memoryStream, Encoding.UTF8 );
xs.Serialize ( xmlTextWriter, myObject);
string result = Encoding.UTF8.GetString(memoryStream .ToArray());

После прочтения этого question я спросил себя: почему бы не использовать StringWriter? Это кажется намного проще.

XmlSerializer ser = new XmlSerializer(typeof(MyObject));
StringWriter writer = new StringWriter();
ser.Serialize(writer, myObject);
serializedValue = writer.ToString();

Другая проблема заключалась в том, что первый пример, сгенерированный XML, я не мог просто записать в столбец XML базы данных SQL Server 2005.

Первый вопрос: есть ли причина, по которой я не должен использовать StringWriter для сериализации объекта, когда мне это понадобится после строки? Я никогда не нашел результат, используя StringWriter при поиске в Google.

Во-вторых, конечно: если вы не должны делать это с помощью StringWriter (по каким-либо причинам), что было бы правильным и правильным способом?


Дополнение:

Как уже упоминалось в обоих ответах, я продолжу рассмотрение проблемы XML-DB.

При записи в базу данных я получил следующее исключение:

System.Data.SqlClient.SqlException: Разбор XML: строка 1, символ 38, невозможно переключить кодировку

Для строки

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

Я взял строку, созданную из XmlTextWriter, и просто разместил ее как xml. Это не сработало (ни с ручным введением в БД).

Впоследствии я попробовал ручную вставку (просто запись INSERT INTO...) с кодировкой = "utf-16", которая также не удалась. После этого полностью удаляется кодировка. После этого я переключился на код StringWriter и вуаля - он работал.

Проблема: я действительно не понимаю, почему.

Кристиан Хейтер: С этими тестами я не уверен, что мне нужно использовать utf-16 для записи в БД. Не назначил ли кодирование UTF-16 (в теге xml)?

Ответ 1

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

SQL Server работает по аналогичному принципу; любая строка, переданная в столбец xml, должна кодироваться как UTF-16. SQL Server отклонит любую строку, где объявление XML не указывает UTF-16. Если XML-объявление отсутствует, то стандарт XML требует, чтобы он по умолчанию был UTF-8, поэтому SQL Server также отклонит это.

Имея это в виду, здесь приведены некоторые полезные методы для преобразования.

public static string Serialize<T>(T value) {

    if(value == null) {
        return null;
    }

    XmlSerializer serializer = new XmlSerializer(typeof(T));

    XmlWriterSettings settings = new XmlWriterSettings()
    {
        Encoding = new UnicodeEncoding(false, false), // no BOM in a .NET string
        Indent = false,
        OmitXmlDeclaration = false
    };

    using(StringWriter textWriter = new StringWriter()) {
        using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) {
            serializer.Serialize(xmlWriter, value);
        }
        return textWriter.ToString();
    }
}

public static T Deserialize<T>(string xml) {

    if(string.IsNullOrEmpty(xml)) {
        return default(T);
    }

    XmlSerializer serializer = new XmlSerializer(typeof(T));

    XmlReaderSettings settings = new XmlReaderSettings();
    // No settings need modifying here

    using(StringReader textReader = new StringReader(xml)) {
        using(XmlReader xmlReader = XmlReader.Create(textReader, settings)) {
            return (T) serializer.Deserialize(xmlReader);
        }
    }
}

Ответ 2

Одна из проблем с StringWriter заключается в том, что по умолчанию он не позволяет вам устанавливать кодировку, которую она рекламирует, - поэтому вы можете закончить с XML-документом, рекламирующим его кодировку как UTF-16, что означает, что вам нужно кодировать его как UTF-16, если вы напишете его в файл. У меня есть небольшой класс, который поможет в этом:

public sealed class StringWriterWithEncoding : StringWriter
{
    public override Encoding Encoding { get; }

    public StringWriterWithEncoding (Encoding encoding)
    {
        Encoding = encoding;
    }    
}

Или, если вам нужен только UTF-8 (который мне больше всего нужен):

public sealed class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding => Encoding.UTF8;
}

Что касается того, почему вы не могли сохранить свой XML в базе данных, вам нужно будет дать нам более подробную информацию о том, что произошло, когда вы пытались, если вы хотите, чтобы мы могли диагностировать/исправить его.

Ответ 3

Прежде всего, остерегайтесь найти старые примеры. Вы нашли тот, который использует XmlTextWriter, который устарел от .NET 2.0. XmlWriter.Create следует использовать вместо этого.

Вот пример сериализации объекта в столбце XML:

public void SerializeToXmlColumn(object obj)
{
    using (var outputStream = new MemoryStream())
    {
        using (var writer = XmlWriter.Create(outputStream))
        {
            var serializer = new XmlSerializer(obj.GetType());
            serializer.Serialize(writer, obj);
        }

        outputStream.Position = 0;
        using (var conn = new SqlConnection(Settings.Default.ConnectionString))
        {
            conn.Open();

            const string INSERT_COMMAND = @"INSERT INTO XmlStore (Data) VALUES (@Data)";
            using (var cmd = new SqlCommand(INSERT_COMMAND, conn))
            {
                using (var reader = XmlReader.Create(outputStream))
                {
                    var xml = new SqlXml(reader);

                    cmd.Parameters.Clear();
                    cmd.Parameters.AddWithValue("@Data", xml);
                    cmd.ExecuteNonQuery();
                }
            }
        }
    }
}

Ответ 4

public static T DeserializeFromXml<T>(string xml)
{
    T result;
    XmlSerializerFactory serializerFactory = new XmlSerializerFactory();
    XmlSerializer serializer =serializerFactory.CreateSerializer(typeof(T));

    using (StringReader sr3 = new StringReader(xml))
    {
        XmlReaderSettings settings = new XmlReaderSettings()
        {
            CheckCharacters = false // default value is true;
        };

        using (XmlReader xr3 = XmlTextReader.Create(sr3, settings))
        {
            result = (T)serializer.Deserialize(xr3);
        }
    }

    return result;
}

Ответ 5

<TL; DR> На самом деле проблема довольно проста: вы не сопоставляете объявленную кодировку (в объявлении XML) с типом данных входного параметра. Если вы вручную добавили <?xml version="1.0" encoding="utf-8"?><test/> в строку, то объявление SqlParameter типа SqlDbType.Xml или SqlDbType.NVarChar даст вам "неспособный переключить кодировку "ошибка. Затем, вставляя вручную через T-SQL, так как вы переключили объявленную кодировку на utf-16, вы явно вставляли строку VARCHAR (без префикса в верхнем регистре "N", следовательно, кодировка 8 -bit такие как UTF-8), а не строка NVARCHAR (с префиксом "N" в верхнем регистре, следовательно, кодировка 16 -bit UTF -1 6 LE).

Исправление должно было быть таким простым:

  1. В первом случае при добавлении объявления с указанием encoding="utf-8": просто не добавляйте объявление XML.
  2. Во втором случае при добавлении объявления с указанием encoding="utf-16": либо
    1. просто не добавляйте декларацию XML, ИЛИ
    2. просто добавьте "N" к типу входного параметра: SqlDbType.NVarChar вместо SqlDbType.VarChar :-) (или, возможно, даже переключитесь на использование SqlDbType.Xml)

(Подробный ответ ниже)


Все ответы здесь являются слишком сложными и ненужными (независимо от 121 и 184 повышенных голосов за ответы Кристиана и Джона соответственно). Они могут предоставить рабочий код, но на самом деле никто из них не ответит на вопрос. Проблема в том, что никто по-настоящему не понял вопроса, который, в конечном счете, касается того, как работает тип данных XML в SQL Server. Ничего против этих двух явно интеллигентных людей, но этот вопрос не имеет ничего общего с сериализацией в XML. Сохранение XML-данных в SQL Server намного проще, чем подразумевается здесь.

На самом деле не имеет значения, как создается XML, если вы следуете правилам создания XML-данных в SQL Server. У меня есть более подробное объяснение (включая рабочий пример кода, иллюстрирующий пункты, изложенные ниже) в ответе на этот вопрос: как устранить ошибку "невозможно переключить кодировку" при вставке XML в SQL Server, но основные принципы таковы:

  1. Декларация XML является необязательной
  2. Тип данных XML хранит строки всегда как UCS-2/UTF -1 6 LE
  3. Если ваш XML является UCS-2/UTF -1 6 LE, то вы:
    1. передайте данные как NVARCHAR(MAX) или XML/SqlDbType.NVarChar (maxsize = -1) или SqlDbType.Xml, или, если используется строковый литерал, то перед ним должен стоять префикс "N" в верхнем регистре.
    2. если указывается объявление XML, оно должно быть либо "UCS-2", либо "UTF -1 6" (здесь нет реальной разницы)
  4. Если ваш XML кодируется в формате 8 -bit (например, "UTF-8"/"iso-8859 -1"/"Windows -1 252"), то вы:
    1. необходимо указать декларацию XML, ЕСЛИ кодировка отличается от кодовой страницы, указанной в параметре Сортировка базы данных по умолчанию
    2. Вы должны передать данные как VARCHAR(MAX)/SqlDbType.VarChar (maxsize = -1), или, если используется строковый литерал, тогда он не должен иметь префикс "N" в верхнем регистре.
    3. Какие бы 8 -bit кодировки ни использовались, "кодировка", отмеченная в объявлении XML, должна соответствовать фактической кодировке байтов.
    4. Кодировка 8 -bit будет преобразована в UTF -1 6 LE с помощью типа данных XML

Имея в виду изложенные выше моменты и учитывая, что строки в.NET всегда имеют формат UTF -1 6 LE/UCS-2 LE (между ними нет никакой разницы с точки зрения кодирования), мы можем ответить на ваши вопросы:

Есть ли причина, по которой я не должен использовать StringWriter для сериализации объекта, когда он мне понадобится как строка впоследствии?

Нет, ваш код StringWriter выглядит нормально (по крайней мере, я не вижу проблем в моем ограниченном тестировании с использованием 2-го блока кода из вопроса).

Не сработает ли тогда установка кодировки в UTF -1 6 (в теге xml)?

Нет необходимости предоставлять декларацию XML. Если он отсутствует, кодировка считается UTF -1 6 LE, если вы передаете строку в SQL Server как NVARCHAR (то есть SqlDbType.NVarChar) или XML (то есть SqlDbType.Xml). Кодировка считается кодовой страницей 8 -bit по умолчанию, если она передается как VARCHAR (то есть SqlDbType.VarChar). Если у вас есть какие-либо нестандартные символы ASCII (т.е. Значения 128 и выше) и вы передаете их как VARCHAR, то вы, скорее всего, увидите "?" для персонажей BMP и "??" для дополнительных символов в качестве SQL Server преобразует строку UTF -1 6 из.NET в строку 8 -bit текущей кодовой страницы базы данных, прежде чем преобразовать ее обратно в UTF -1 6/UCS-2. Но вы не должны получать никаких ошибок.

С другой стороны, если вы укажете декларацию XML, вы должны перейти на SQL Server, используя соответствующий тип данных 8 -bit или 16 -bit. Поэтому, если у вас есть объявление о том, что кодировкой является UCS-2 или UTF -1 6, вы должны передать как SqlDbType.NVarChar или SqlDbType.Xml. Или, если у вас есть объявление о том, что кодирование является одним из 8 вариантов -bit (т.е. UTF-8, Windows-1252, iso-8859-1 и т.д.), SqlDbType.VarChar вы должны передать его как SqlDbType.VarChar. Неверное сопоставление объявленной кодировки с правильным 8 или 16 -bit типом данных SQL Server приведет к полученной вами ошибке "невозможно переключить кодировку".

Например, используя ваш код сериализации StringWriter -based, я просто распечатал полученную строку XML и использовал ее в SSMS. Как вы можете видеть ниже, объявление XML включено (потому что StringWriter не имеет опции OmitXmlDeclaration как у XmlWriter), что не вызывает проблем, если вы передаете строку как правильный тип данных 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>

Как вы можете видеть, он даже обрабатывает символы, выходящие за рамки стандартного ASCII, учитывая, что - это кодовая точка BMP U + 1234, а 😸 - дополнительная кодовая точка символов U + 1F638. Тем не менее, следующее:

-- No upper-case "N" prefix on the string literal, hence VARCHAR:
DECLARE @Xml XML = '<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';

приводит к следующей ошибке:

Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 39, unable to switch the encoding

Ergo, кроме всего этого объяснения, полное решение вашего первоначального вопроса:

Вы явно передавали строку как SqlDbType.VarChar. Переключитесь на SqlDbType.NVarChar и он будет работать без необходимости выполнять дополнительный шаг удаления декларации XML. Это предпочтительнее, чем хранить SqlDbType.VarChar и удалять декларацию XML, потому что это решение предотвратит потерю данных, когда XML содержит символы нестандартного ASCII. Например:

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

Как видите, на этот раз ошибки нет, но теперь происходит потеря данных 🙀.

Ответ 6

Возможно, это было рассмотрено в другом месте, но просто изменение строки кодирования источника XML на "utf-16" позволяет вставлять XML в тип XML-типа SQL Server.

using (DataSetTableAdapters.SQSTableAdapter tbl_SQS = new DataSetTableAdapters.SQSTableAdapter())
{
    try
    {
        bodyXML = @"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><test></test>";
        bodyXMLutf16 = bodyXML.Replace("UTF-8", "UTF-16");
        tbl_SQS.Insert(messageID, receiptHandle, md5OfBody, bodyXMLutf16, sourceType);
    }
    catch (System.Data.SqlClient.SqlException ex)
    {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

В результате весь текст XML вставляется в поле типа данных "xml", но строка "header" удаляется. То, что вы видите в результирующей записи, просто

<test></test>

Использование метода сериализации, описанного в записи "Ответ", является способом включения исходного заголовка в целевое поле, но результат заключается в том, что оставшийся XML-текст заключен в тег XML <string></string>.

Адаптер таблицы в коде - это класс, автоматически созданный с помощью Visual Studio 2013 "Добавить новый источник данных: мастер". Пять параметров для метода Вставка сопоставляются с полями в таблице SQL Server.