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