web-dev-qa-db-ja.com

C#でXMLドキュメントの一部のみを逆シリアル化する方法

これが私が解決しようとしている問題の架空の例です。 C#で作業していて、次のようなXMLがあるとします。

<?xml version="1.0" encoding="utf-8"?>
<Cars>
  <Car>
    <StockNumber>1020</StockNumber>
    <Make>Nissan</Make>
    <Model>Sentra</Model>
  </Car>
  <Car>
    <StockNumber>1010</StockNumber>
    <Make>Toyota</Make>
    <Model>Corolla</Model>
  </Car>
  <SalesPerson>
    <Company>Acme Sales</Company>
    <Position>
       <Salary>
          <Amount>1000</Amount>
          <Unit>Dollars</Unit>
    ... and on... and on....
  </SalesPerson>
</Cars>

salesPerson内のXMLは非常に長く、サイズがメガバイトになる場合があります。タグをデシリアライズしたいのですがbutSalesPerson XML要素をデシリアライズせずに、「後で使用するため」の生の形式で保持します。

基本的に、これをXMLのオブジェクト表現として使用できるようにしたいと考えています。

[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
    [XmlArrayItem(typeof(Car))]
    public Car[] Car { get; set; }

    public Stream SalesPerson { get; set; }
}

public class Car
{
    [System.Xml.Serialization.XmlElementAttribute("StockNumber")]
    public string StockNumber{ get; set; }

    [System.Xml.Serialization.XmlElementAttribute("Make")]
    public string Make{ get; set; }

    [System.Xml.Serialization.XmlElementAttribute("Model")]
    public string Model{ get; set; }
}

ここで、CarsオブジェクトのSalesPersonプロパティには、XmlSerializerを介して実行された後、<SalesPerson> xml要素内にある未加工のxmlを持つストリームが含まれます。

これはできますか? XMLドキュメントの「一部」のみをデシリアライズすることを選択できますか?

ありがとう! -マイク

pS から盗まれたxmlの例XMLドキュメントをデシリアライズする方法

27
Mike

少し古いスレッドかもしれませんが、とにかく投稿します。私は同じ問題を抱えていました(1MBを超えるファイルから10kbほどのデータをデシリアライズする必要がありました)。メインオブジェクト(デシリアライザである必要のあるInnerObjectを持っています)にIXmlSerializableインターフェイスを実装し、ReadXmlメソッドを変更しました。

入力としてxmlTextReaderがあり、最初の行はXMLタグまで読み取ることです。

reader.ReadToDescendant("InnerObjectTag"); //tag which matches the InnerObject

次に、逆シリアル化するオブジェクトのタイプのXMLSerializerを作成し、それを逆シリアル化します

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

this.innerObject = serializer.Deserialize(reader.ReadSubtree()); //this gives serializer the part of XML that is for  the innerObject data

reader.close(); //now skip the rest 

これにより、シリアル化を解除する時間を大幅に節約でき、XMLの一部のみを読み取ることができました(ファイルを説明する詳細の一部であり、ユーザーがファイルをロードするかどうかを判断するのに役立ちます)。

38
user271807

User271807から受け入れられた answer は素晴らしい解決策ですが、次のような内部例外を持つ例外を回避するためにフラグメントのxmlルートを設定する必要があることもわかりました。

...xmlns=''> was not expected

この例外は、このXMLドキュメントの内部認証要素のみを逆シリアル化しようとしたときに発生しました。

<?xml version=""1.0"" encoding=""UTF-8""?>
<Api>
  <Authentication>                       
      <sessionid>xxx</sessionid>
      <errormessage>xxx</errormessage>                
  </Authentication>
</ApI>

したがって、私はこの拡張メソッドを再利用可能なソリューションとして作成することになりました-警告にはメモリリークが含まれています。以下を参照してください:

public static T DeserializeXml<T>(this string @this, string innerStartTag = null)
        {
            using (var stringReader = new StringReader(@this))
            using (var xmlReader = XmlReader.Create(stringReader)) {
                if (innerStartTag != null) {
                    xmlReader.ReadToDescendant(innerStartTag);
                    var xmlSerializer = new XmlSerializer(typeof(T), new XmlRootAttribute(innerStartTag));
                    return (T)xmlSerializer.Deserialize(xmlReader.ReadSubtree());
                }
                return (T)new XmlSerializer(typeof(T)).Deserialize(xmlReader);
            }
        }

2017年3月20日更新:以下のコメントが指摘するように、XmlSerializerのコンストラクターの1つを使用すると、メモリリークの問題が発生するため、以下に示すように、キャッシュソリューションを使用することになりました:

    /// <summary>
    ///     Deserialize XML string, optionally only an inner fragment of the XML, as specified by the innerStartTag parameter.
    /// </summary>
    public static T DeserializeXml<T>(this string @this, string innerStartTag = null) {
        using (var stringReader = new StringReader(@this)) {
            using (var xmlReader = XmlReader.Create(stringReader)) {
                if (innerStartTag != null) {
                    xmlReader.ReadToDescendant(innerStartTag);
                    var xmlSerializer = CachingXmlSerializerFactory.Create(typeof (T), new XmlRootAttribute(innerStartTag));
                    return (T) xmlSerializer.Deserialize(xmlReader.ReadSubtree());
                }
                return (T) CachingXmlSerializerFactory.Create(typeof (T), new XmlRootAttribute("AutochartistAPI")).Deserialize(xmlReader);
            }
        }
    }
/// <summary>
///     A caching factory to avoid memory leaks in the XmlSerializer class.
/// See http://dotnetcodebox.blogspot.dk/2013/01/xmlserializer-class-may-result-in.html
/// </summary>
public static class CachingXmlSerializerFactory {
    private static readonly ConcurrentDictionary<string, XmlSerializer> Cache = new ConcurrentDictionary<string, XmlSerializer>();
    public static XmlSerializer Create(Type type, XmlRootAttribute root) {
        if (type == null) {
            throw new ArgumentNullException(nameof(type));
        }
        if (root == null) {
            throw new ArgumentNullException(nameof(root));
        }
        var key = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", type, root.ElementName);
        return Cache.GetOrAdd(key, _ => new XmlSerializer(type, root));
    }
    public static XmlSerializer Create<T>(XmlRootAttribute root) {
        return Create(typeof (T), root);
    }
    public static XmlSerializer Create<T>() {
        return Create(typeof (T));
    }
    public static XmlSerializer Create<T>(string defaultNamespace) {
        return Create(typeof (T), defaultNamespace);
    }
    public static XmlSerializer Create(Type type) {
        return new XmlSerializer(type);
    }
    public static XmlSerializer Create(Type type, string defaultNamespace) {
        return new XmlSerializer(type, defaultNamespace);
    }
}

クラスにISerializableインターフェイスを実装することで、シリアル化の方法を制御できます。これは、メソッドシグネチャ(SerializationInfo情報、StreamingContextコンテキスト)を備えたコンストラクタも意味することに注意してください。

ただし、ストリーミングメカニズムを使用する必要がない場合は、Linq to XMLを使用して同じことを行う方が簡単で、長期的に維持するのが簡単になるため、ストリーミングで本当にこれを行う必要があるかどうかをよく確認してください。用語(IMO)

3
Tim Jarvis

前のコメンターは、XMLがここでのバッキングストアの最良の選択ではない可能性があるというコメントで正しいと思います。

スケールの問題があり、変換のようにXMLで得られる他のいくつかの優れた点を活用していない場合は、データベースをデータに使用したほうがよい場合があります。あなたがしている操作は本当にそのモデルにより適合しているようです。

これで本当にあなたの質問に答えられないことはわかっていますが、私はあなたが使用する可能性のある代替ソリューションを強調したいと思いました。適切なデータベースと適切なORマッパー(.netTiers、NHibernate、または最近ではLINQ to SQL/Entity Frameworkなど)を使用すると、コードベースの残りの部分に最小限の変更を加えるだけで、バックアップと実行が可能になります。

2
Anderson Imes

通常、XMLの逆シリアル化は、箱から出してすべてかゼロかという提案なので、おそらくカスタマイズする必要があります。完全な逆シリアル化を行わないと、SalesPerson要素内でxmlが不正な形式になり、ドキュメントが無効になるリスクがあります。

そのリスクを受け入れる用意がある場合は、基本的なテキスト解析を行って、SalesPerson要素をプレーンテキスト処理機能を使用して別のドキュメントに分割し、XMLを処理することをお勧めします。

これは、XMLが常に正しい答えとは限らない理由の良い例です。

1
MikeD

タイプXmlElementとしてSalesPersonプロパティを定義してみてください。これは、XMLシリアル化を使用するASMX Webサービスからの出力で機能します。入力でも機能すると思います。全体を期待します<SalesPerson>要素をXmlElementに追加します。

1
John Saunders

XmlReader、XPathDocument、LINQ-to-XMLなどの軽量メソッドを使用して、手動でXmlから読み取ることをお勧めします。

3つのプロパティのみを読み取る必要がある場合、そのノードから手動で読み取るコードを記述し、シリアル化/非シリアル化に依存する代わりに、その実行方法を完全に制御できると思います。

0
Bogdan_Ch

CarsクラスにIXmlSerializableインターフェイスを実装し、次にReadXml(XmlReader)メソッドでは、Car要素を読み取って逆シリアル化しますが、SalesPerson要素に到達すると、そのサブツリーを文字列として読み取り、StreamWriterを使用してテキストコンテンツ上にStreamを構築します。

XmlSerializerにSalesPerson要素を書き出させたくない場合は、[XmlIgnore]属性を使用します。 CarsクラスをXML表現にシリアライズするときに何をしたいのかわかりません。 Streamで表されるSalesPersonのXML表現をシリアル化できる一方で、SalesPersonの逆シリアル化のみを防止しようとしていますか?

具体的な実装が必要な場合は、おそらくこのコード例を提供できます。

0
Oppositional

やりたいことは、SalesPerson要素を解析して文字列として保持することだけである場合は、「デシリアライゼーション」ではなくXsl変換を使用する必要があります。一方、SalesPerson要素を解析して、他のすべての非SalesPerson要素からメモリ内のオブジェクトにのみデータを入力する場合は、Xsl変換も適しています。ファイルが非常に大きい場合は、それらを分離し、Xslを使用してさまざまなxmlファイルを組み合わせて、必要なときにのみSalesPerson I/Oが発生するようにすることを検討してください。

0
devlord