web-dev-qa-db-ja.com

XMLシリアル化にStringWriterを使用する

現在、オブジェクトをシリアル化する簡単な方法を探しています(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());

これを読んだ後 question 私は自分自身に尋ねました。ずっと簡単に思えます。

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を使用してObjectをシリアル化しない理由はありますか?グーグルでStringWriterを使用して結果を見つけたことはありません。

2番目は、もちろん:StringWriterで(何らかの理由で)行うべきではない場合、これは適切で正しい方法でしょうか?


添加:

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

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

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

文字列用

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

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

その後、encoding = "utf-16"を使用して手動挿入(INSERT INTO ...を書き込むだけ)を試みましたが、これも失敗しました。エンコーディングの削除は完全に機能しました。その結果、私はStringWriterコードに戻り、出来上がりました-うまくいきました。

問題:理由が本当にわかりません。

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

92
StampedeXV

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

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

  1. 最初のケースでは、encoding="utf-8":を示す宣言を追加するときに、XML宣言を追加しないでください。
  2. 2番目のケースでは、encoding="utf-16":またはeither を示す宣言を追加する場合
    1. 単にXML宣言を追加しない、または
    2. 入力パラメータタイプに「N」を追加するだけです:SqlDbType.NVarCharの代わりにSqlDbType.VarChar :-)(またはSqlDbType.Xmlを使用するように切り替えることもできます)

(詳細な応答は以下です)


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

SQL ServerでXMLデータを作成する方法の規則に従う限り、XMLがどのように生成されるかは実際には関係ありません。この質問の答えには、より徹底的な説明があります(以下に概説するポイントを説明するためのサンプルコードを含む): SQL Serverに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)としてデータを渡す必要があります。または、文字列リテラルを使用する場合は、notを接頭辞として付ける必要があります大文字の「N」。
    3. 使用される8ビットエンコーディングが何であれ、XML宣言に記載されている「エンコーディング」は、バイトの実際のエンコーディングと一致する必要があります。
    4. 8ビットエンコーディングは、XMLデータ型によってUTF-16 LEに変換されます

上記のポイントを念頭に置いて、.NETの文字列がalwaysUTF-16 LE/UCS-2 LEである場合、and (エンコーディングに関しては違いはありません)、あなたの質問に答えることができます:

後で文字列として必要なときに、StringWriterを使用してObjectをシリアル化するべきではない理由はありますか?

いいえ、StringWriterコードは問題ないようです(少なくとも、質問の2番目のコードブロックを使用した制限付きテストで問題は発生していません)。

エンコーディングをUTF-16(xmlタグ内)に設定しても機能しませんか?

XML宣言を提供する必要はありません。欠落している場合、エンコードはUTF-16 LEと想定されますif文字列をNVARCHAR(つまりSqlDbType.NVarChar)またはXML(つまりSqlDbType.Xml)。 VARCHAR(つまりSqlDbType.VarChar)として渡す場合、エンコードはデフォルトの8ビットコードページと見なされます。非標準のASCII文字(つまり、128以上の値)があり、VARCHARとして渡される場合、「?」が表示される可能性があります。 BMP文字と「??」 SQL Serverでの補助文字の場合、UTF-16文字列を.NETから現在のデータベースのコードページの8ビット文字列に変換してから、UTF-16/UCS-2に変換します。ただし、エラーは発生しないはずです。

一方、XML宣言を指定する場合、mustは、一致する8ビットまたは16ビットのデータ型を使用してSQL Serverに渡す必要があります。したがって、エンコーディングがUCS-2またはUTF-16であると宣言している場合、mustSqlDbType.NVarCharまたはSqlDbType.Xmlとして渡す必要があります。または、エンコードが8ビットオプションの1つ(つまり、UTF-8Windows-1252iso-8859-1など)であるという宣言がある場合は、mustSqlDbType.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宣言を削除するよりも優先されます。これは、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>

ご覧のとおり、今回はエラーはありませんが、データ損失thereがあります。

1
Solomon Rutzky

StringWriterの1つの問題は、デフォルトで アドバタイズするエンコードを設定できない です。そのため、エンコードをUTF-16としてアドバタイズする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をデータベースに保存できなかった理由については、それを診断/修正できるようにしたい場合は、試みたときに何が起こったのかについて詳細を提供する必要があります。

203
Jon Skeet

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);
        }
    }
}
126

まず、古い例を見つけることに注意してください。 .NET 2.0で廃止されたXmlTextWriterを使用するものを見つけました。代わりに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();
                }
            }
        }
    }
}
19
John Saunders
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