DTOを調理する方法は?

過去1か月半にわたって、3つのプロジェクトのバックエンドに取り組んできました。 それぞれが、XMLドキュメントの交換を通じてリモートサービスと対話するためのクラスを準備する必要がありました。 特に、メッセージタイプごとにDTOクラスを準備する必要がありました。 各サービスには、開発者からの説明があり、XMLドキュメントの例が含まれていたため、作業は比較的簡単でした:例を取って、xsdユーティリティで実行し、クラスを取得し、タイプを修正し、不足しているフィールド/プロパティを追加して、テストします。 オペレーションは日常的なもので、数十回のクラスを経て考える必要がなくなったため、私の考えでは、開発プロセスをスピードアップする方法や出力を改善する方法についての考えを蓄積し始めました。 両方が判明しました。



TLDR
調理のために、xsdからDTO半製品を取り出し、プリミティブ型のラッパーを追加し、砂糖(暗黙の演算子)を追加し、電子レンジに30分間入れて、便利なデータ転送オブジェクトを取得します。



最初に、サンプルのXMLドキュメントをxsdユーティリティに送ります。 xsd.exeを終了した後のDTOオブジェクトは次のようになります。



//   :  xsd [System.SerializableAttribute] [XmlTypeAttribute(AnonymousType = true)] public class DtoProduct { private string productidfield; private string productnamefield; private string productpricefield; /*...*/ [XmlElement] public string productid { get{return this.productidfield;} set{this.productidfield = value;} } [XmlElement] public string productname { get{return this.productnamefield;} set{this.productnamefield = value;} } [XmlElement] public string productprice { get{return this.productpricefield;} set{this.productpricefield = value;} } }
      
      





スタイルの問題(Resharperおよび\またはCtrl + Hで処理)に加えて、タイプに問題があります:価格では小数が優れ、IDでは長さが優れています。 正しい型の指定については、DTOのユーザーから感謝されるか、少なくとも彼らが地獄で燃え尽きることを望まないでしょう。 変更を加えると同時に、名前を企業標準に戻します。



 [System.SerializableAttribute] [XmlTypeAttribute(AnonymousType = true)] public class DtoProduct { private decimal _productPrice; //... [XmlElement("productprice")] public decimal ProductPrice { get{return _productPrice;} set{_productPrice = value;} } }
      
      





すでに優れていますが、(de)シリアル化には問題があります。リモートサービスには独自のデータ転送形式がある場合があります。 これは、特に日付と小数値の場合に当てはまります。 さらに、特定のタイプ(guid、code、mail)が見つかる場合があります。



別の問題:デフォルト値。 文字列プロパティの値がnullの場合、プロパティはシリアル化されません(プロパティに値が割り当てられていない場合、プロパティはシリアル化されません)。 Double、int、boolは値型(値型)であり、値nullを受け入れることはできません。 その結果、intプロパティはデフォルトでシリアル化されます(intプロパティに値が割り当てられていない場合は0がシリアル化されます)。 ほとんどの場合、これは害をもたらしませんが、これは回避したいコードで説明されている動作ではありません。



そのため、基本型をシリアル化(デ)するためのルールを作成する必要があります。 例として、「d.dd」(「ドット」区切り文字、区切り文字の後の2文字)としてシリアル化されるMoney(10進数)を考えます。 XmlMoneyWrapperクラスを作成し、IXmlSerializableから継承します。



 public class XmlMoneyWrapper : IXmlSerializable { public decimal Value { get; set; } //     public override string ToString() { return Value.ToString("0.00", CultureInfo.InvariantCulture); } #region IXmlSerializable Members public XmlSchema GetSchema() { return null; } public void ReadXml(XmlReader reader) { string value = reader.ReadString(); // TODO change to TryParse? try { Value = Decimal.Parse(value, new NumberFormatInfo { NumberDecimalSeparator = "." }); } catch (Exception exc) { String err = String.Format("Can't deserialize string {0} to decimal. Expected number decimal separator is dot \".\"", value); throw new SerializationException(err, exc); } reader.Read(); } public void WriteXml(XmlWriter writer) { writer.WriteString(ToString()); } #endregion }
      
      





そして、DTOを変更します。



 [System.SerializableAttribute] [XmlTypeAttribute(AnonymousType = true)] public class DtoProduct { private XmlMoneyWrapper _productPrice; //... [XmlElement("productprice")] public XmlMoneyWrapper ProductPrice //    ,   { get { return _productPrice; } set { _productPrice = value; } } }
      
      





nullとして開始されるNullableプロパティを作成しました。 DTOユーザーがシリアル化形式について考える必要がなくなりました。 ただし、DTOでの作業はより複雑になりました。 ifチェック(product.ProductPrice> 10.00)をif(product.ProductPrice.Value> 10.00)に置き換える必要があります。



結論:暗黙のキャスト演算子オーバーロードするには、砂糖を大さじ2杯追加する必要があります。



 public static implicit operator XmlMoneyWrapper(decimal arg) // decimal to XmlMoneyWrapper { XmlMoneyWrapper res = new XmlMoneyWrapper { Value = arg }; return res; }
      
      





 public static implicit operator decimal (XmlMoneyWrapper arg) // XmlMoneyWrapper to decimal { return arg.Value; }
      
      





これで、ユーザーはif(product.ProductPrice> 10.00)のようなコードを再び使用できます。 同時に、暗黙のゴーストに関する警告をクラスのコメントに含める(およびコミットする)必要があります。 さらに、DTOを使用している同僚は暗黙の演算子を覚えていない可能性があるため、使用例を追加する価値があります。 結局のところ、私たちの目標は最近研究された機能を誇示することではないのですか?



残念ながら、一部の型は暗黙的なキャストと互換性がありません。 たとえば、制限された長さの文字列:オーバーロードインターフェイス



 public static implicit operator XmlLimitedStringWrapper(string arg)
      
      





最大文字列長の引数を入れる余地はありません。 同じ理由で、カスタムのデフォルト値で還元可能なクラスを作成することはできません。 このような状況では、フィールドとプロパティを操作するだけです。 たとえば、制限された最大長の文字列クラスは次のように使用できます。



 [System.SerializableAttribute] [XmlTypeAttribute(AnonymousType = true)] public class DtoProduct { private readonly XmlLimitedStringWrapper _productName = new XmlLimitedStringWrapper(16); // Create string with maxLength = 16. // ... // Max symbols: 16. [XmlElement("productname")] public string ProductName { get{return _productName = _productName.Value;} set{_productName.Value = value;} } }
      
      





これらの操作の結果、未加工のDTO処理製品はかなり便利な製品に変わります。 すべての書式設定ロジックはユーザーから隠されており、ユーザーはコードで通常の基本タイプを使用できます。 (感情に応じた)DTOクラスの作成は、以前よりも少し時間がかかります。 Nullableラッパーにより、トラフィックがわずかに減少する場合があります。 すべてのXmlPrimitiveTypeWrapperタイプの開発には、約1日かかります(ユニットテスト付き)。 次のプロジェクトでは、ほとんど変更されないため、既製のラッパーを使用できます。



欠点は標準です。利便性にはアーキテクチャの複雑さが必要であり、同僚は新しいクラスでDTOを作成する時間が必要になる場合があります。



All Articles