<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>
Як бачите, помилок цього разу немає, але зараз є втрата даних 🙀.