Как решить проблему "неспособность переключить кодировку" при вставке 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
:
- Является высоко оптимизированным (для хранения) типом, который преобразует входящий XML в двоичный формат (который зарегистрирован где-то на сайте
msdn
). Оптимизация включает: - Преобразование чисел и дат из строки (как они есть в XML) в двоичные представления. Если элемент или атрибут помечен информацией типа (для этого может потребоваться указать сборку XML-схемы). Значение "1234567" хранится как 4-байтовый "int" вместо 14-байтовой строки UTF-16 из 7 цифр.
- Имена элементов и атрибутов хранятся в словаре и задаются числовым идентификатором. Этот числовой идентификатор используется в структуре дерева XML. Значение "
<ElementName>...</ElementName>
" занимает 27 символов (т.е. 54 байта) в строковой форме, но только 11 символов (т.е. 22 байта) при сохранении в типе XML
. И это для одного его экземпляра. Несколько экземпляров занимают дополнительные кратные 54 байта. Но в типе XML каждый экземпляр занимает только пространство этого числового идентификатора, скорее всего это 4-байтовый int.
- Сохраняет строки как UTF-16 Little Endian, всегда. Скорее всего, поэтому XML-декларация не сохраняется: она совершенно не нужна, поскольку она всегда одна и та же, поскольку атрибут "Кодирование" не может измениться.
- Никакая декларация XML не предполагает кодирование UTF-16, а не UTF-8.
-
Может иметь 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".
-
Когда входная строка 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();