Использование 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).
Исправление должно было быть таким простым:
- В первом случае при добавлении объявления с указанием
encoding="utf-8"
: просто не добавляйте объявление XML. - Во втором случае при добавлении объявления с указанием
encoding="utf-16"
: либо - просто не добавляйте декларацию XML, ИЛИ
- просто добавьте "N" к типу входного параметра:
SqlDbType.NVarChar
вместо SqlDbType.VarChar
:-) (или, возможно, даже переключитесь на использование SqlDbType.Xml
)
(Подробный ответ ниже)
Все ответы здесь являются слишком сложными и ненужными (независимо от 121 и 184 повышенных голосов за ответы Кристиана и Джона соответственно). Они могут предоставить рабочий код, но на самом деле никто из них не ответит на вопрос. Проблема в том, что никто по-настоящему не понял вопроса, который, в конечном счете, касается того, как работает тип данных XML в SQL Server. Ничего против этих двух явно интеллигентных людей, но этот вопрос не имеет ничего общего с сериализацией в XML. Сохранение XML-данных в SQL Server намного проще, чем подразумевается здесь.
На самом деле не имеет значения, как создается XML, если вы следуете правилам создания XML-данных в SQL Server. У меня есть более подробное объяснение (включая рабочий пример кода, иллюстрирующий пункты, изложенные ниже) в ответе на этот вопрос: как устранить ошибку "невозможно переключить кодировку" при вставке XML в SQL Server, но основные принципы таковы:
- Декларация XML является необязательной
- Тип данных XML хранит строки всегда как UCS-2/UTF -1 6 LE
- Если ваш XML является UCS-2/UTF -1 6 LE, то вы:
- передайте данные как
NVARCHAR(MAX)
или XML
/SqlDbType.NVarChar
(maxsize = -1) или SqlDbType.Xml
, или, если используется строковый литерал, то перед ним должен стоять префикс "N" в верхнем регистре. - если указывается объявление XML, оно должно быть либо "UCS-2", либо "UTF -1 6" (здесь нет реальной разницы)
- Если ваш XML кодируется в формате 8 -bit (например, "UTF-8"/"iso-8859 -1"/"Windows -1 252"), то вы:
- необходимо указать декларацию XML, ЕСЛИ кодировка отличается от кодовой страницы, указанной в параметре Сортировка базы данных по умолчанию
- Вы должны передать данные как
VARCHAR(MAX)
/SqlDbType.VarChar
(maxsize = -1), или, если используется строковый литерал, тогда он не должен иметь префикс "N" в верхнем регистре. - Какие бы 8 -bit кодировки ни использовались, "кодировка", отмеченная в объявлении XML, должна соответствовать фактической кодировке байтов.
- Кодировка 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.