<TL; DR> На самом деле проблема довольно проста: вы не сопоставляете заявленную кодировку (в объявлении XML) с типом данных входного параметра. Если вы вручную добавили <?xml version="1.0" encoding="utf-8"?><test/>
строку, то объявление SqlParameter
типа как типа SqlDbType.Xml
или SqlDbType.NVarChar
выдаст вам ошибку «Невозможно переключить кодировку». Затем, при вставке вручную через T-SQL, поскольку вы переключили объявленную кодировку на значение «быть» utf-16
, вы явно вставляли VARCHAR
строку (без префикса «N» в верхнем регистре, следовательно, 8-битная кодировка, такая как UTF-8) а не NVARCHAR
строку (с префиксом «N» в верхнем регистре, следовательно, 16-битная кодировка UTF-16 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-16 LE.
- Если ваш XML-код - UCS-2 / UTF-16 LE, вы:
- передать данные как
NVARCHAR(MAX)
или XML
/ SqlDbType.NVarChar
(maxsize = -1) или SqlDbType.Xml
, или, если используется строковый литерал, он должен иметь префикс «N» в верхнем регистре.
- если указывается объявление XML, оно должно быть либо «UCS-2», либо «UTF-16» (здесь нет реальной разницы)
- Если ваш XML закодирован в 8-битном формате (например, "UTF-8" / "iso-8859-1" / "Windows-1252"), вы:
- необходимо указать объявление XML, ЕСЛИ кодировка отличается от кодовой страницы, указанной в параметрах сортировки базы данных по умолчанию
- вы должны передавать данные как
VARCHAR(MAX)
/ SqlDbType.VarChar
(maxsize = -1), или, если используется строковый литерал, он не должен иметь префикс с заглавной буквой «N».
- Какая бы 8-битная кодировка ни использовалась, «кодировка», указанная в объявлении XML, должна соответствовать фактической кодировке байтов.
- 8-битная кодировка будет преобразована в UTF-16 LE по типу данных XML.
Принимая во внимание изложенные выше моменты и учитывая, что строки в .NET всегда имеют формат UTF-16 LE / UCS-2 LE (нет разницы между ними с точки зрения кодировки), мы можем ответить на ваши вопросы:
Есть ли причина, по которой мне не следует использовать StringWriter для сериализации объекта, когда он мне впоследствии понадобится в виде строки?
Нет, с вашим StringWriter
кодом все в порядке (по крайней мере, я не вижу проблем в моем ограниченном тестировании с использованием второго блока кода из вопроса).
Тогда не будет ли работать кодировка UTF-16 (в теге xml)?
Предоставлять XML-декларацию необязательно. Если он отсутствует, предполагается, что кодировка будет UTF-16 LE, если вы передадите строку в SQL Server как NVARCHAR
(т.е. SqlDbType.NVarChar
) или XML
(т.е. SqlDbType.Xml
). Предполагается, что кодировка является 8-битной кодовой страницей по умолчанию, если она передается как VARCHAR
(т.е. SqlDbType.VarChar
). Если у вас есть какие-либо символы нестандартного ASCII (например, значения 128 и выше) и вы передаете их как VARCHAR
, то вы, вероятно, увидите "?" для символов BMP и "??" для дополнительных символов, поскольку SQL Server преобразует строку UTF-16 из .NET в 8-битную строку кодовой страницы текущей базы данных перед ее обратным преобразованием в UTF-16 / UCS-2. Но ошибок не должно быть.
С другой стороны, если вы укажете объявление XML, вы должны передать в SQL Server соответствующий 8-битный или 16-битный тип данных. Поэтому, если у вас есть объявление, в котором указано, что используется кодировка UCS-2 или UTF-16, вы должны передать как SqlDbType.NVarChar
или SqlDbType.Xml
. Или, если у вас есть заявление о том , что кодирование является одним из 8-битных вариантов (то есть UTF-8
, Windows-1252
, iso-8859-1
и т.д.), то вы должны пройти как SqlDbType.VarChar
. Несоответствие заявленной кодировки правильному 8- или 16-битному типу данных SQL Server приведет к полученной вами ошибке «Невозможно переключить кодировку».
Например, используя ваш StringWriter
код сериализации, я просто распечатал полученную строку 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
Таким образом, если отбросить все эти объяснения, полное решение вашего исходного вопроса:
Вы явно передавали строку как 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>
Как видите, на этот раз ошибки нет, но теперь есть потеря данных 🙀.