<TL; DR> Problem jest raczej prosty: nie dopasowujesz zadeklarowanego kodowania (w deklaracji XML) do typu danych parametru wejściowego. Jeśli ręcznie dodano <?xml version="1.0" encoding="utf-8"?><test/>
do ciągu, a następnie zadeklarowanie plikuSqlParameter
jako typu SqlDbType.Xml
lub SqlDbType.NVarChar
spowodowałoby błąd „nie można zmienić kodowania”. Następnie, podczas ręcznego wstawiania przez T-SQL, ponieważ zmieniłeś zadeklarowane kodowanie na utf-16
, wyraźnie wstawiałeś VARCHAR
ciąg (bez prefiksu wielkiej litery „N”, stąd kodowanie 8-bitowe, takie jak UTF-8) a nie NVARCHAR
ciągiem (poprzedzonym dużą literą „N”, stąd 16-bitowe kodowanie UTF-16 LE).
Poprawka powinna być tak prosta, jak:
- W pierwszym przypadku dodając deklarację o treści
encoding="utf-8"
: po prostu nie dodawaj deklaracji XML.
- W drugim przypadku przy dodawaniu deklaracji z podaniem
encoding="utf-16"
: albo
- po prostu nie dodawaj deklaracji XML, LUB
- po prostu dodaj „N” do typu parametru wejściowego:
SqlDbType.NVarChar
zamiast SqlDbType.VarChar
:-) (lub nawet przełącz się na używanie SqlDbType.Xml
)
(Szczegółowa odpowiedź znajduje się poniżej)
Wszystkie odpowiedzi tutaj są zbyt skomplikowane i niepotrzebne (niezależnie od 121 i 184 głosów pozytywnych odpowiednio na odpowiedzi Christiana i Jona). Mogą dostarczyć działający kod, ale żaden z nich tak naprawdę nie odpowiada na pytanie. Problem polega na tym, że nikt tak naprawdę nie zrozumiał pytania, które ostatecznie dotyczy tego, jak działa typ danych XML w SQL Server. Nie ma nic przeciwko tym dwóm wyraźnie inteligentnym ludziom, ale to pytanie nie ma nic wspólnego z serializacją do XML. Zapisywanie danych XML w SQL Server jest znacznie łatwiejsze niż to, co jest tutaj sugerowane.
Nie ma znaczenia, w jaki sposób powstaje XML, o ile przestrzegasz zasad tworzenia danych XML w SQL Server. Mam dokładniejsze wyjaśnienie (w tym działający przykładowy kod ilustrujący punkty przedstawione poniżej) w odpowiedzi na to pytanie: Jak rozwiązać błąd „nie można zmienić kodowania” podczas wstawiania XML do SQL Server , ale podstawy są następujące:
- Deklaracja XML jest opcjonalna
- Typ danych XML przechowuje łańcuchy zawsze jako UCS-2 / UTF-16 LE
- Jeśli Twój XML to UCS-2 / UTF-16 LE, to:
- przechodzą w danych, jak też
NVARCHAR(MAX)
i XML
/ SqlDbType.NVarChar
(MAXSIZE = 1) lub SqlDbType.Xml
, czy za pomocą łańcuch znaków to musi być poprzedzone dużą literę „n”.
- jeśli określasz deklarację XML, musi to być „UCS-2” lub „UTF-16” (nie ma tu żadnej różnicy)
- Jeśli Twój kod XML jest 8-bitowy (np. „UTF-8” / „iso-8859-1” / „Windows-1252”), to:
- należy określić deklarację XML, JEŚLI kodowanie jest inne niż strona kodowa określona przez domyślne sortowanie bazy danych
- musisz przekazać dane jako
VARCHAR(MAX)
/ SqlDbType.VarChar
(maxsize = -1), lub jeśli używasz literału łańcuchowego, nie możesz poprzedzać go wielką literą „N”.
- Niezależnie od zastosowanego kodowania 8-bitowego, „kodowanie” odnotowane w deklaracji XML musi odpowiadać faktycznemu kodowaniu bajtów.
- Kodowanie 8-bitowe zostanie przekonwertowane na UTF-16 LE przez typ danych XML
Mając na uwadze powyższe punkty i biorąc pod uwagę, że ciągi znaków w .NET to zawsze UTF-16 LE / UCS-2 LE (nie ma między nimi różnicy w zakresie kodowania), możemy odpowiedzieć na Twoje pytania:
Czy istnieje powód, dla którego nie powinienem używać StringWriter do serializacji obiektu, gdy potrzebuję go później jako ciągu?
Nie, Twój StringWriter
kod wydaje się być w porządku (przynajmniej nie widzę żadnych problemów w moich ograniczonych testach z użyciem drugiego bloku kodu z pytania).
Czy wtedy ustawienie kodowania na UTF-16 (w tagu xml) nie zadziała?
Podawanie deklaracji XML nie jest konieczne. Gdy go brakuje, zakłada się, że kodowanie to UTF-16 LE, jeśli przekazujesz ciąg do SQL Server jako NVARCHAR
(tj. SqlDbType.NVarChar
) Lub XML
(tj SqlDbType.Xml
.). Zakłada się, że kodowanie jest domyślną 8-bitową stroną kodową, jeśli jest przekazywane jako VARCHAR
(tj SqlDbType.VarChar
.). Jeśli masz jakieś niestandardowe znaki ASCII (tj. Wartości 128 i więcej) i przekazujesz je jako VARCHAR
, prawdopodobnie zobaczysz znak „?” dla znaków BMP i „??” dla znaków uzupełniających, ponieważ SQL Server skonwertuje ciąg znaków UTF-16 z .NET na 8-bitowy ciąg strony kodowej bieżącej bazy danych przed konwersją z powrotem na UTF-16 / UCS-2. Ale nie powinieneś otrzymać żadnych błędów.
Z drugiej strony, jeśli określisz deklarację XML, musisz przekazać ją do SQL Server przy użyciu pasującego 8-bitowego lub 16-bitowego typu danych. Więc jeśli masz deklarację stwierdzającą, że kodowanie to UCS-2 lub UTF-16, musisz przekazać jako SqlDbType.NVarChar
lub SqlDbType.Xml
. Albo, jeśli masz deklarację stwierdzającą, że kodowanie jest jedną z opcji 8-bitowych (to znaczy UTF-8
, Windows-1252
, iso-8859-1
itp), to musi przejść w jak SqlDbType.VarChar
. Niezgodność zadeklarowanego kodowania z odpowiednim 8- lub 16-bitowym typem danych programu SQL Server spowoduje wystąpienie błędu „nie można zmienić kodowania”.
Na przykład, używając StringWriter
kodu serializacji opartego na twoim , po prostu wydrukowałem wynikowy ciąg XML i użyłem go w SSMS. Jak widać poniżej, deklaracja XML jest włączone (bo StringWriter
nie ma opcji do OmitXmlDeclaration
jak XmlWriter
robi), która nie stanowi żadnego problemu, tak długo, jak przekazać ciąg jako prawidłowy typ danych 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>
Jak widać, obsługuje nawet znaki spoza standardowego ASCII, biorąc pod uwagę, że ሴ
jest to punkt kodowy BMP U + 1234 i 😸
jest to dodatkowy punkt kodowy znaków U + 1F638. Jednak następujące:
-- No upper-case "N" prefix on the string literal, hence VARCHAR:
DECLARE @Xml XML = '<?xml version="1.0" encoding="utf-16"?>
<string>Test ሴ😸</string>';
powoduje następujący błąd:
Msg 9402, Level 16, State 1, Line XXXXX
XML parsing: line 1, character 39, unable to switch the encoding
Ergo, pomijając te wszystkie wyjaśnienia, pełne rozwiązanie twojego pierwotnego pytania brzmi:
Wyraźnie przekazałeś ciąg jako SqlDbType.VarChar
. Przełącz się na SqlDbType.NVarChar
i będzie działać bez konieczności przechodzenia przez dodatkowy krok polegający na usunięciu deklaracji XML. Jest to preferowane rozwiązanie zamiast zachowywania SqlDbType.VarChar
i usuwania deklaracji XML, ponieważ to rozwiązanie zapobiega utracie danych, gdy XML zawiera niestandardowe znaki ASCII. Na przykład:
-- 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>
Jak widać, tym razem nie ma błędu, ale teraz nastąpiła utrata danych 🙀.