XMLシリアル化のためのStringWriterの使用


99

現在、オブジェクトをシリアル化する簡単な方法を探しています(C#3)。

私はいくつかの例をググって、次のようなものを思いつきました:

MemoryStream memoryStream = new MemoryStream ( );
XmlSerializer xs = new XmlSerializer ( typeof ( MyObject) );
XmlTextWriter xmlTextWriter = new XmlTextWriter ( memoryStream, Encoding.UTF8 );
xs.Serialize ( xmlTextWriter, myObject);
string result = Encoding.UTF8.GetString(memoryStream .ToArray());

この質問を読んだ後、私は自分自身に質問しましたが、なぜStringWriterを使用しないのですか?はるかに簡単なようです。

XmlSerializer ser = new XmlSerializer(typeof(MyObject));
StringWriter writer = new StringWriter();
ser.Serialize(writer, myObject);
serializedValue = writer.ToString();

もう1つの問題は、最初の例でXMLが生成されて、SQL Server 2005 DBのXML列に書き込むことができなかったことです。

最初の質問は次のとおりです。後で文字列として必要になったときに、StringWriterを使用してオブジェクトをシリアル化しない理由はありますか?グーグルでStringWriterを使用した結果は見つかりませんでした。

2番目はもちろんです。もしStringWriterを(何らかの理由で)使用してはならない場合は、どちらが適切で正しい方法でしょうか。


添加:

両方の回答ですでに言及されているので、XMLからDBへの問題についてさらに説明します。

データベースに書き込むときに、次の例外が発生しました。

System.Data.SqlClient.SqlException:XML解析:行1、文字38、エンコードを切り替えることができません

文字列用

<?xml version="1.0" encoding="utf-8"?><test/>

XmlTextWriterから作成した文字列を取り、そこにxmlとして配置しました。これは機能しませんでした(手動でDBに挿入した場合も同様)。

その後、手動での挿入(INSERT INTO ...を書くだけ)をencoding = "utf-16"で試みましたが、これも失敗しました。その後、エンコーディングを完全に削除しました。その結果、私は再びStringWriterコードに切り替えて、できあがりました-うまくいきました。

問題:理由がよくわかりません。

Christian Hayterで:これらのテストでは、DBへの書き込みにutf-16を使用する必要があるかどうかはわかりません。エンコーディングをUTF-16(xmlタグで)に設定すると機能しませんか?


1
個人的な経験をします。SQL ServerはUTF-16のみを受け入れます。それ以外のものを渡すと、SQL Server XMLパーサーとそのデータ変換の試みに翻弄されます。私はそれをだます方法を見つけるのではなく、直接UTF-16を渡すだけで、常に機能します。
クリスチャンヘイター

これをどのようにデータベースに書き込んでいますか?文字列、またはバイトの配列を渡しますか、それともストリームに書き込みますか?後者の2つの形式のいずれかである場合は、宣言したエンコードがバイナリデータの実際のエンコードと一致していることを確認する必要があります。
Jon Skeet、

ふw。MS SQL Management Studioでクエリとして手動で試してみました。「コード化された」試行は文字列に書き込まれ、それが文字列として書き込むO / Rマッパーに渡されました(私がフォローできる限り)。実際、私の質問で挙げた2つの例で作成された文字列を渡しています。
StampedeXV


1
実際に私の質問に答えると信じているので、受け入れた回答を変更します。他の回答は私が仕事を続けるのに役立ちましたが、Stackoverflowの目的では、ソロモンの回答は他の人が何が起こったかをよりよく理解するのに役立つと思います。[免責事項]:本当に答えを確認する時間を見つけられませんでした。
StampedeXV

回答:


1

<TL; DR>問題はかなり単純ですが、実際には(XML宣言で)宣言されたエンコードを入力パラメーターのデータ型と照合していません。手動で追加した場合<?xml version="1.0" encoding="utf-8"?><test/>、文字列には、宣言SqlParameter型であるSqlDbType.Xmlか、SqlDbType.NVarCharあなたに「エンコーディングを切り替えることができません」というエラーを与えるだろう。次に、T-SQLを介して手動で挿入すると、宣言されたエンコーディングをに切り替えたためutf-16VARCHAR文字列(大文字の「N」が前に付いていないため、UTF-8などの8ビットエンコーディングではない)を明確に挿入していました。NVARCHAR文字列ではありません(先頭に大文字の「N」が付いているため、16ビットのUTF-16 LEエンコーディング)。

修正は次のように簡単なはずです。

  1. 最初のケースでは、次の宣言を追加する場合、encoding="utf-8"単にXML宣言を追加しないでください。
  2. 宣言を追加するときに、第2のケースでは、述べencoding="utf-16":いずれか
    1. 単にXML宣言を追加しない、または
    2. 入力パラメータタイプに「N」を追加するだけです::)のSqlDbType.NVarChar代わりにSqlDbType.VarChar(または多分に切り替えて使用しますSqlDbType.Xml

(詳細な応答は以下のとおりです)


ここでのすべての回答は複雑すぎて不必要です(クリスチャンの回答に対する121票とジョンの回答に対する184票に関係なく)。彼らは実際に機能するコードを提供するかもしれませんが、実際には誰も質問に答えません。問題は、最終的にSQL ServerのXMLデータ型がどのように機能するかについての質問を誰も本当に理解していなかったことです。これらの2人の明らかにインテリジェントな人々に対しては何もありませんが、この質問はXMLへのシリアル化とはほとんど関係がありません。XMLデータをSQL Serverに保存することは、ここに示されているものよりもはるかに簡単です。

SQL ServerでXMLデータを作成する方法のルールに従う限り、XMLがどのように生成されるかは重要ではありません。この質問の回答には、より詳細な説明(以下に概説するポイントを示すためのサンプルコードを含む)があります。SQLServerにXMLを挿入するときに「エンコードを切り替えられません」エラーを解決する方法ですが、基本は次のとおりです。

  1. XML宣言はオプションです
  2. XMLデータ型は文字列を常にUCS-2 / UTF-16 LEとして保存します
  3. XMLがUCS-2 / UTF-16 LEの場合、次のようになります。
    1. NVARCHAR(MAX)またはXML/ SqlDbType.NVarChar(maxsize = -1)またはとしてデータを渡しSqlDbType.Xmlます。文字列リテラルを使用する場合は、先頭に大文字の「N」を付ける必要があります。
    2. XML宣言を指定する場合は、「UCS-2」または「UTF-16」のいずれかでなければなりません(ここでは実際の違いはありません)
  4. XMLが8ビットでエンコードされている場合(たとえば、「UTF-8」/「iso-8859-1」/「Windows-1252」)、次のようになります。
    1. エンコーディングがデータベースのデフォルトの照合で指定されたコードページと異なる場合は、XML宣言を指定する必要があります。
    2. VARCHAR(MAX)/ SqlDbType.VarChar(maxsize = -1)としてデータを渡す必要があります。または、文字列リテラルを使用する場合、先頭に大文字の「N」を付けないでください。
    3. 使用される8ビットエンコーディングが何であれ、XML宣言に記載されている「エンコーディング」は、バイトの実際のエンコーディングと一致している必要があります。
    4. 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.NVarCharSqlDbType.Xml。それとも、あなたはエンコーディングが(すなわち、8ビットの中の選択肢の一つである旨の宣言がある場合はUTF-8Windows-1252iso-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>

ご覧のとおり、今回はエラーは発生していませんが、データ損失が発生しています🙀。


私は基本的に2つの質問を1つにまとめたので、私はこの複雑すぎる答えの理由だったと思います。私はあなたの簡潔な答えが本当に好きで、次回XMLをDBに保存する必要があるときに試してみます。ですから、私がこれを正しく理解しているとしたら、XMLをDBに保存する際の課題について説明しました。Jon Skeetは、XML(UTF-16を除く)を操作するときにStringWriterを使用する際の問題を抱えており、Christian Hayterはそれを操作するための優れた方法を提供しています。
StampedeXV 2018

@StampedeXV私は私の答えを更新しました(明確にするためのいくつかの変更+ポイントをよりよく説明するための新しいもの)。うまくいけば、これらの答えはどちらもそれ自体で十分ですが、質問に回答するために必ずしも必要ではないことは明らかです。C#/ .NETでのXMLシリアル化を扱いますが、この質問はSQL ServerでのXMLの保存に関するものです。彼らは知っておくべき情報を提供し、あなたが最初に提供したよりも優れたコードかもしれませんが、どちらも(ここでは他のどれも)本当に話題になっています。しかし、これは十分に文書化されたものではないため、混乱が生じます。
ソロモンRutzky 2018

@StampedeXV私の改訂は意味がありましたか?わかりやすくなるように、概要セクションを上部に追加しました。長い話:質問に詳細を含めなかった何か他のことが起こっていない限り、コードは99%正しかったようで、大文字を1つ追加するだけで修正できた可能性があります " N "。特別なエンコーディングは必要なく、Christianのコードは優れていますが、私のテストでは、XML宣言の後にCRLFを配置することを除いて、2番目のコードブロックと同じシリアル化を返すことがわかりました。きっとSqlDbType.NVarCharまたはに変わりましたXml
ソロモンルツキー

まだ自分でチェックする時間を見つけようとしています。これは確かに論理的に聞こえますが、受け入れられた回答を変更するには十分かどうかはわかりません。
StampedeXV 2018

216

問題の1つStringWriterは、デフォルトでは、アドバタイズするエンコーディングを設定できないことです。つまり、XMLドキュメントでエンコーディングをUTF-16としてアドバタイズすることになります。つまり、それをファイルに書き込みます。私はそれを助けるために小さなクラスを持っています:

public sealed class StringWriterWithEncoding : StringWriter
{
    public override Encoding Encoding { get; }

    public StringWriterWithEncoding (Encoding encoding)
    {
        Encoding = encoding;
    }    
}

または、UTF-8のみが必要な場合(これが私がよく必要とするものすべてです):

public sealed class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding => Encoding.UTF8;
}

XMLをデータベースに保存できなかった理由については、それを診断/修正できるようにしたい場合は、試行したときに何が起こったかの詳細を提供する必要があります。


データベースの問題について詳しく説明しました。質問を参照してください。
StampedeXV

4
悲しいStringWriterことに、エンコーディングは考慮されていませんが、それでもなおそうではありません。気の利いた小さな方法のおかげです:)
Chau

2
また、「XML解析:行1、文字38、エンコーディングを切り替えることができない」は、「settings.Indent = false; settings.OmitXmlDeclaration = false;」によって解決できます
MGE

私は通常、正しいエンコーディングでa MemoryStreamとa StreamWriterを使用するだけでこれを回避します。StreamWriter であるTextWriter(タイプXmlWriter.Create結局、カスタマイズ可能な符号化と期待は)。
Nyerguds、2015年

2
@Nyerguds:したがって、この種のものでNugetパッケージを作成すると、いつでも簡単にアクセスできます。基本的には他のいくつかの要件に関するコードの可読性を損なうよりも、そうしたいのです。
Jon Skeet

126

XMLドキュメントを.NET文字列にシリアル化する場合、エンコーディングはUTF-16に設定する必要があります。文字列は内部的にUTF-16として格納されるため、これが意味のある唯一のエンコーディングです。別のエンコーディングでデータを保存する場合は、代わりにバイト配列を使用します。

SQL Serverも同様の原理で機能します。列に渡される文字列はxml、UTF-16としてエンコードする必要があります。SQL Serverは、XML宣言でUTF-16が指定されていない文字列を拒否します。XML宣言が存在しない場合、XML標準ではデフォルトでUTF-8にする必要があるため、SQL Serverもそれを拒否します。

これを念頭に置いて、変換を行うためのユーティリティメソッドをいくつか示します。

public static string Serialize<T>(T value) {

    if(value == null) {
        return null;
    }

    XmlSerializer serializer = new XmlSerializer(typeof(T));

    XmlWriterSettings settings = new XmlWriterSettings()
    {
        Encoding = new UnicodeEncoding(false, false), // no BOM in a .NET string
        Indent = false,
        OmitXmlDeclaration = false
    };

    using(StringWriter textWriter = new StringWriter()) {
        using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) {
            serializer.Serialize(xmlWriter, value);
        }
        return textWriter.ToString();
    }
}

public static T Deserialize<T>(string xml) {

    if(string.IsNullOrEmpty(xml)) {
        return default(T);
    }

    XmlSerializer serializer = new XmlSerializer(typeof(T));

    XmlReaderSettings settings = new XmlReaderSettings();
    // No settings need modifying here

    using(StringReader textReader = new StringReader(xml)) {
        using(XmlReader xmlReader = XmlReader.Create(textReader, settings)) {
            return (T) serializer.Deserialize(xmlReader);
        }
    }
}

質問の追加を参照してください。私のテスト結果がわかりません。DBが常にUTF-16を必要とする/必要とする/必要とするというあなたの声明に矛盾しているようです。
StampedeXV

9
あなたはしていない UTF-16としてエンコードする必要があります-しかし、あなたは確認する必要があり、あなたが一致するものを使用エンコードStringWriterを期待します。私の答えを見てください。内部ストレージ形式はここでは関係ありません。
Jon Skeet、

わかりました。私の新しい例では、エンコーディングを完全に除外することで、DBがどのエンコーディングを使用するかを自分で決定しました。それが機能した理由です。私はそれが今正しいと理解していますか?
StampedeXV、

1
@SteveC:すみません、私の間違い。私はVBからコードを手動で変換しましたが、そのコードNothingは暗黙的に任意の型に変換できます。Deserializeコードを修正しました。Serialize警告はReSharperの-唯一、反対しない、独自のコンパイラでなければならず、それを行うのは合法です。
クリスチャンヘイター、2011

1
Jon Skeetのコメントを拡張すると、いいえ、UTF-16は必要ありません。これを示す具体的な例については、stackoverflow.com / a / 8998183/751158を参照してください。
ジーゼマー2013

20

まず、古い例を見つけることに注意してください。を使用しているものが見つかりましたXmlTextWriter。これは.NET 2.0の時点で非推奨です。XmlWriter.Create代わりに使用する必要があります。

オブジェクトをXML列にシリアル化する例を次に示します。

public void SerializeToXmlColumn(object obj)
{
    using (var outputStream = new MemoryStream())
    {
        using (var writer = XmlWriter.Create(outputStream))
        {
            var serializer = new XmlSerializer(obj.GetType());
            serializer.Serialize(writer, obj);
        }

        outputStream.Position = 0;
        using (var conn = new SqlConnection(Settings.Default.ConnectionString))
        {
            conn.Open();

            const string INSERT_COMMAND = @"INSERT INTO XmlStore (Data) VALUES (@Data)";
            using (var cmd = new SqlCommand(INSERT_COMMAND, conn))
            {
                using (var reader = XmlReader.Create(outputStream))
                {
                    var xml = new SqlXml(reader);

                    cmd.Parameters.Clear();
                    cmd.Parameters.AddWithValue("@Data", xml);
                    cmd.ExecuteNonQuery();
                }
            }
        }
    }
}

2
私はこれに1回しか投票できませんが、これがここでの一番の回答になるに値します。結局のところ、XmlReaderが解析できる限り、どのエンコーディングが宣言または使用されているかは問題ではありません。事前に解析されてデータベースに送信され、DBは文字エンコーディング(UTF-16など)について何も知る必要がありません。特に、XML宣言は、どのメソッドを使用してデータを挿入するかに関係なく、データベース内のデータでさえ永続化されないことに注意してください。ここや他の回答に示されているように、XMLを追加の変換で実行しても無駄にしないでください。
ジーゼマー

1
public static T DeserializeFromXml<T>(string xml)
{
    T result;
    XmlSerializerFactory serializerFactory = new XmlSerializerFactory();
    XmlSerializer serializer =serializerFactory.CreateSerializer(typeof(T));

    using (StringReader sr3 = new StringReader(xml))
    {
        XmlReaderSettings settings = new XmlReaderSettings()
        {
            CheckCharacters = false // default value is true;
        };

        using (XmlReader xr3 = XmlTextReader.Create(sr3, settings))
        {
            result = (T)serializer.Deserialize(xr3);
        }
    }

    return result;
}

-1

他の場所で説明されている可能性がありますが、XMLソースのエンコード行を「utf-16」に変更するだけで、XMLをSQL Serverの「xml」データ型に挿入できます。

using (DataSetTableAdapters.SQSTableAdapter tbl_SQS = new DataSetTableAdapters.SQSTableAdapter())
{
    try
    {
        bodyXML = @"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><test></test>";
        bodyXMLutf16 = bodyXML.Replace("UTF-8", "UTF-16");
        tbl_SQS.Insert(messageID, receiptHandle, md5OfBody, bodyXMLutf16, sourceType);
    }
    catch (System.Data.SqlClient.SqlException ex)
    {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

その結果、すべてのXMLテキストが「xml」データタイプフィールドに挿入されますが、「header」行は削除されます。結果のレコードに表示されるのは、

<test></test>

「回答済み」エントリで説明されているシリアル化方法を使用すると、元のヘッダーをターゲットフィールドに含めることができますが、その結果、残りのXMLテキストがXML <string></string>タグで囲まれます。

コード内のテーブルアダプターは、Visual Studio 2013の「新しいデータソースの追加:ウィザード」を使用して自動的に構築されるクラスです。Insertメソッドの5つのパラメーターは、SQL Serverテーブルのフィールドにマップされます。


2
置き換えますか?こりゃ愉快だ。
mgilberties 2016年

2
真剣に-これをしないでください。ずっと。「UTF-8」と書かれた散文をxmlに含めたいとしたらどうなるでしょう-データを私が言わなかったものに変更しただけです!
Tim Abell

2
コードの誤りを指摘していただきありがとうございます。bodyXML.Replace( "UTF-8"、 "UTF-16")ではなく、XMLヘッダーをUTF-8からUTF-16に変更することに焦点を当てたコードが必要です。私が実際に指摘しようとしていたのは、ソースXMLのヘッダーにこの変更を加えることです。それから、XMLデータ型フィールドを使用してXMLの本文をSQLテーブルレコードに挿入し、ヘッダーを取り除くことができます。理由は今は思い出せません(4年前です!)当時、その結果は役に立ちました。そして、はい、 'Replace'を使用して間違えた間違い。それが起こります。
DLG 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.