<TL; DR>問題はかなり単純ですが、実際には(XML宣言で)宣言されたエンコードを入力パラメーターのデータ型と照合していません。手動で追加した場合<?xml version="1.0" encoding="utf-8"?><test/>、文字列には、宣言SqlParameter型であるSqlDbType.Xmlか、SqlDbType.NVarCharあなたに「エンコーディングを切り替えることができません」というエラーを与えるだろう。次に、T-SQLを介して手動で挿入すると、宣言されたエンコーディングをに切り替えたためutf-16、VARCHAR文字列(大文字の「N」が前に付いていないため、UTF-8などの8ビットエンコーディングではない)を明確に挿入していました。NVARCHAR文字列ではありません(先頭に大文字の「N」が付いているため、16ビットのUTF-16 LEエンコーディング)。
修正は次のように簡単なはずです。
- 最初のケースでは、次の宣言を追加する場合、
encoding="utf-8"単にXML宣言を追加しないでください。
- 宣言を追加するときに、第2のケースでは、述べ
encoding="utf-16":いずれか
- 単にXML宣言を追加しない、または
- 入力パラメータタイプに「N」を追加するだけです::)の
SqlDbType.NVarChar代わりにSqlDbType.VarChar(または多分に切り替えて使用しますSqlDbType.Xml)
(詳細な応答は以下のとおりです)
ここでのすべての回答は複雑すぎて不必要です(クリスチャンの回答に対する121票とジョンの回答に対する184票に関係なく)。彼らは実際に機能するコードを提供するかもしれませんが、実際には誰も質問に答えません。問題は、最終的にSQL ServerのXMLデータ型がどのように機能するかについての質問を誰も本当に理解していなかったことです。これらの2人の明らかにインテリジェントな人々に対しては何もありませんが、この質問はXMLへのシリアル化とはほとんど関係がありません。XMLデータをSQL Serverに保存することは、ここに示されているものよりもはるかに簡単です。
SQL ServerでXMLデータを作成する方法のルールに従う限り、XMLがどのように生成されるかは重要ではありません。この質問の回答には、より詳細な説明(以下に概説するポイントを示すためのサンプルコードを含む)があります。SQLServerにXMLを挿入するときに「エンコードを切り替えられません」エラーを解決する方法ですが、基本は次のとおりです。
- 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ビットエンコーディングは、XMLデータ型によってUTF-16 LEに変換されます
上記のポイントを念頭に置き、.NETの文字列は常に UTF-16 LE / UCS-2 LEであることを前提として(エンコーディングに関してはそれらに違いはありません)、次の質問に答えることができます。
後で文字列として必要になったときに、StringWriterを使用してオブジェクトをシリアル化しない理由はありますか?
いいえ、StringWriterコードは問題ないようです(少なくとも、質問の2番目のコードブロックを使用した限定的なテストでは問題は発生しません)。
エンコーディングをUTF-16(xmlタグで)に設定すると機能しませんか?
XML宣言を提供する必要はありません。それがない場合、文字列をSQL ServerにNVARCHAR(ie SqlDbType.NVarChar)またはXML(ie SqlDbType.Xml)として渡すと、エンコーディングはUTF-16 LEであると見なされます。として渡される場合VARCHAR(つまりSqlDbType.VarChar)、エンコーディングはデフォルトの8ビットコードページであると想定されます。非標準のASCII文字(128以上の値)があり、として渡されているVARCHAR場合は、「?」が表示される可能性があります。BMP文字と「??」SQL Serverは、UTF-16文字列を.NETから現在のデータベースのコードページの8ビット文字列に変換してから、UTF-16 / UCS-2に戻すため、補助文字の場合。ただし、エラーは発生しません。
一方、XML宣言を指定する場合は、一致する8ビットまたは16ビットのデータ型を使用してSQL Serverに渡す必要があります。あなたがエンコードが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>
ご覧のように、ሴBMPコードポイントU + 1234であり、😸補助文字コードポイントU + 1F638であることから、標準のASCIIを超える文字も処理できます。ただし、次のとおりです。
-- 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に非標準のASCII文字が含まれている場合のデータ損失を防ぐため、XML宣言を保持および削除するよりも優先されます。例えば:
-- 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>
ご覧のとおり、今回はエラーは発生していませんが、データ損失が発生しています🙀。