web-dev-qa-db-ja.com

XElement.Parseとクエリとシリアル化(厳密に型指定されたオブジェクト)

多くのSOAPベースのXMLサービスと相互作用するかなり大きなコードベースがあります。

これらのサービスはそれぞれ、1対nのサービス呼び出しを行います。一般的な低レベルのWebサービス呼び出しは、次のようになります(簡略化)。

_public XElement ExecuteWebService(string xmlRequest) 
_

WCFを使用してSOAPメッセージを送信し、応答を返します。GetReaderAtBodyContents()を使用して本文を取得し、次のようにその文字列をXMLElementに変換します。

_XElement.Parse(response)
_

次に、アプリケーションの残りのレイヤー全体でそのXElementを使用します。 XmlSerialization属性でマークされている強く型付けされたデータコントラクトまたはクラスはありません。

XElementは任意の有効なXML構造である可能性があるため、このタイプの構造ではテストの作成が非常に困難になります。また、XElementが渡されるときに、XElementの読み取り、解析、更新を行うコード行が多数あります。

このタイプの構造には正当な理由がありますか?それはパフォーマンス上の理由で行われたと言われ、より柔軟です。

XElementの解析と読み取りは、1回のシリアル化(データコントラクト、XMLシリアル化)よりも本当に高速ですか?これは、厳密に型指定されたオブジェクトモデルを使用する場合よりも本当に柔軟ですか?

他のシステムでは、理解と保守が簡単なため、常にシリアル化と強く型付けされたオブジェクトを使用しました。このXElementアプローチが有効かどうかはわかりません。

2
Jon Raynor

私はいくつかの研究とテストを行いました、そしてここに私の結果があります:

分析

.NetプラットフォームでWebサービスのメッセージを読み書きする場合、いくつかの可能性があります。

  • XmlSerializerを使用した強く型付けされたクラス
  • IXmlSerializableを実装する厳密に型指定されたクラス
  • データコントラクトシリアライザーを使用した厳密に型指定されたクラス
  • XElement/Documentへの読み込みとLinq to Xmlの使用
  • カスタム

XmlSerializerは、要素と属性の両方をシリアル化できます。これは、レガシーまたは既存のメッセージ構造を処理するときのデフォルトの選択です。データモデルクラスを作成する場合、パブリックプロパティまたはメンバーを適切に属性付けして、発信または着信XMLを生成または使用できます。

IXmlSerializableの実装は、XmlSerializerの使用と非常に似ていますが、GetSchema(), ReadXml(), and WriteXml().を実装してXmlの読み取りと書き込みを管理するコードを記述します。このメソッドは、XmlReaderとXmlWritersを使用して、Xmlメッセージの読み取りと書き込みを行います。

データコントラクトシリアライザーも、実装と使用が非常に簡単です。属性をサポートしていないため、Xmlを扱う際のグリーンフィールド開発に適しています。データコントラクトシリアライザーは、メッセージ構造が重要ではないコードファーストシナリオに最適です。

既存のメッセージ構造が存在する場合、データコントラクトシリアライザーを使用することは困難または実行不可能である可能性があります。これらの場合、デフォルトでXmlSerializerに戻す必要があります。XmlSerializerを使用すると、着信および発信メッセージ構造のフォーマットをより細かく制御できます。

データコントラクトシリアライザーはWCFの既定のシリアライザーであり、通常、Xmlをシリアル化するときに、Xmlシリアライザーと同等またはそれ以上のパフォーマンス向上を提供します。データコントラクトシリアライザーは、JSONにシリアル化することもでき、メッセージを処理するときのより近代的なアプローチです。

データコントラクトシリアライザーは、維持するコードの量が最も少なく、優れたパフォーマンスを提供するため、常にグリーンフィールド開発の最初の選択肢です。

XElement/XDocumentの読み込みと解析により、Xmlメッセージの読み取りと書き込みの際の柔軟性が大幅に向上します。

覚えておくべきことのいくつか:

  • XElement/XDocumentは厳密に型指定されておらず、Xmlドキュメントの表現です
  • XElement/XDocumentから情報をプルするコードを記述する必要があります。これはLinq to Xmlでは簡単ですが、複雑さが生じる可能性があります。
  • 必要に応じて、文字列値を適切なデータ型に変換する必要があります。
  • メッセージ構造の内部(XPathなど)を知っている必要があります。
  • コードでXElementおよびXDocument構造を使用する場合、テストは面倒です。
  • シリアル化手法よりも多くのコードが必要です。

このメソッドは、大規模なXml構造からデータを抽出するのに適しています。たとえば、1つの要素に100要素のXmlメッセージがあり、必要な要素が数個または数個しかない場合は、このメソッドが適しています。メッセージ処理を集中化し、解析されるデータ要素を、コードベース全体で使用できる厳密に型指定されたオブジェクトに配置することをお勧めします。 XElementsやXDocumentをコード内で渡さないでください。テストが困難になります。また、メッセージ処理が集中化されていない場合、異なる方法が使用されているため、コードベース全体でXElement解析が重複する可能性があります。

通常、このメソッドは、少量の解析が行われる場合、シリアル化よりも優れたパフォーマンスを提供します。手動での処理と解析の量が増えると、保守とテストを行うコードが少なくなるためパフォーマンスが少し悪い場合でも、シリアライゼーションがより良いオプションになります。

パフォーマンスが最大の関心事である場合、カスタムアプローチの実装は適切ですが、コードとメンテナンスの観点からはコストがかかります。これは、パフォーマンステストに使用されているいくつかのXmlを生成する単純な実装です。 Xml文字列を受け取り、オブジェクトを作成するコンストラクターがあります。 Xml文字列を作成するためにToString()もオーバーライドしました。

public class FoobarHandRolled
{
    public FoobarHandRolled(string name, int age, bool isContent, DateTime birthDay)
    {
        Name = name;
        Age = age;
        IsContent = isContent;
        BirthDay = birthDay;
    }

    public FoobarHandRolled(string xml)
    {
        if (string.IsNullOrWhiteSpace(xml))
        {
            return;
        }

        SetName(xml);
        SetAge(xml);
        SetIsContent(xml);
        SetBirthday(xml);
    }

    public string Name { get; set; }
    public int Age { get; set; }
    public bool IsContent { get; set; }
    public DateTime BirthDay { get; set; }

    /// <summary>
    ///     Takes this object and creates an XML representation.
    /// </summary>
    /// <returns>An XML string that represents this object.</returns>
    public override string ToString()
    {
        var builder = new StringBuilder();
        builder.Append("<FoobarHandRolled>");

        if (!string.IsNullOrWhiteSpace(Name))
        {
            builder.Append("<Name>" + Name + "</Name>");
        }

        builder.Append("<Age>" + Age + "</Age>");
        builder.Append("<IsContent>" + IsContent + "</IsContent>");
        builder.Append("<BirthDay>" + BirthDay.ToString("yyyy-MM-dd") + "</BirthDay>");
        builder.Append("</FoobarHandRolled>");

        return builder.ToString();
    }

    private void SetName(string xml)
    {
        Name = GetSubString(xml, "<Name>", "</Name>");
    }

    private void SetAge(string xml)
    {
        var ageString = GetSubString(xml, "<Age>", "</Age>");
        int result;
        var success = int.TryParse(ageString, out result);
        if (success)
        {
            Age = result;
        }
    }

    private void SetIsContent(string xml)
    {
        var isContentString = GetSubString(xml, "<IsContent>", "</IsContent>");
        bool result;
        var success = bool.TryParse(isContentString, out result);
        if (success)
        {
            IsContent = result;
        }
    }

    private void SetBirthday(string xml)
    {
        var dateString = GetSubString(xml, "<BirthDay>", "</BirthDay>");
        DateTime result;
        var success = DateTime.TryParseExact(dateString, "yyyy-MM-dd", null, DateTimeStyles.None, out result);
        if (success)
        {
            BirthDay = result;
        }
    }

    private string GetSubString(string xml, string startTag, string endTag)
    {
        var startIndex = xml.IndexOf(startTag, StringComparison.Ordinal);
        if (startIndex < 0)
        {
            return null;
        }

        startIndex = startIndex + startTag.Length;

        var endIndex = xml.IndexOf(endTag, StringComparison.Ordinal);
        if (endIndex < 0)
        {
            return null;
        }

        return xml.Substring(startIndex, endIndex - startIndex);
    }
}

ここでは、文字列解析手法とハードコードされた値を使用して、Xmlメッセージの読み取りと書き込みを行っています。この方法は、追加のコード、メンテナンス、カスタム実装を犠牲にして、最高のパフォーマンスを提供します。この方法は、最高のパフォーマンスを得る必要がない限り、お勧めできません。

パフォーマンスの概要

グラフには、初期パフォーマンス(1回目)の時間と平均1000回の読み取りがリストされます。使用したハードウェアは、Visual Studio 2013と.NET 4.5.2を使用したW530ラップトップでした。プロセッサーは2.80Ghzのi7-3840QMでした。すべてのシリアライザは、ナノ秒の読み取り時間を提供します。ウォームアップ時間は、起動時に適切な初期化を実行することで軽減できます。 XMLシリアライザは、事前にSGENを使用することで軽減できます。

Serializer                      First Time  Average 1000 Reads
XmlSerializer                   2448965         245
Implementing XmlSerializable    2051813         208
Custom                          161105          29
Using XElement/XDocument        247024          113
Data Contact (Json)             1979593         303

すべての時間はティックです。ティックはハードウェアに依存しますが、ガイドラインとして、1秒間におよそ10,000,000ティックがあります。

最終的な推奨事項

サービスがデータを交換するメッセージを設計するときは、次のガイドラインに従う必要があります。

  • 要件を満たすために必要な最小限のデータを交換する
  • 複雑なまたは一般的なデータ構造を回避する
  • データを説明するメタデータを含むメッセージは避けてください
  • データ内のデータを回避する(例:Xml内のXML)
  • メッセージのサイズに注意してください

これらのガイドラインに従うことにより、これらのメッセージを使用するクライアントをより簡単かつ高速にすることができます。

2
Jon Raynor