t4テンプレートを介したマッピング



こんにちは 私たちのプロジェクトは、パフォーマンスの最適化について疑問が生じたとき、すでにそのような段階に達しました。 弱点を分析した後、最適化する可能な方法の1つはAutoMapperを取り除くことでした。AutoMapperは最も抑制的な場所ではありませんが、改善できる場所です。 AutoMapperを使用して、DOオブジェクトをDTOオブジェクトにマップし、WCFサービスを介して送信します。 新しいオブジェクトを作成してフィールドをコピーする、手動で作成されたメソッドの方が高速です。 彼らは手動でマッピングを書きました-喜びのないルーチンで、多くの場合エラー、忘れられたフィールド、忘れられた新しいフィールドがあったので、t4テンプレートを介してマッピング生成を書くことにしました。



実際、プロパティとタイプのリストを確認してコピーを作成する必要がありましたが、デンマーク王国ではすべてがそれほどスムーズではありません。



2つのクラスをリンクするために、[Map]属性が追加されました。 テンプレートの構成では、この属性を持つクラスを探す必要がある2つのプロジェクトが登録されました。 クラスは名前でペアにされ、DTOクラスでは接尾辞「Dto」が存在する場合は切り捨てられました。 しかし、場合によっては、まったく同じように、反対のクラスを接続する必要があり、Nameパラメーターが属性に追加されました。



[Map(Name = "NewsCategory")] public class CategoryDto
      
      





マッピングは拡張メソッドとして生成されます。 すべてがうまくいくようで、フィールドがコピーされます。 しかし、まだ多くの手作業が残っています。 DTOオブジェクトとDOオブジェクトには他のオブジェクトとコレクションが含まれており、生成されたメソッドを使用しているにもかかわらず、それらを手動でマッピングする必要があります。 多くのフィールドは同じ名前を持ち、型の一致は、すでにコンパイルした関係のコレクションにあります。

マッピングは、ネストされたオブジェクトとコレクションを自動的にマップするように拡張されました。 [Map]属性のアクションはプロパティに拡張され、一致しない名前でマップできるようになりました。

結果のコードの例。



  public static DataTransferObject.CategoryDto MapToDto (this DataObjects.NewsCategory item) { if (item == null) return null; var itemDto = new DataTransferObject.CategoryDto (); itemDto.NewsCategoryId = item.NewsCategoryId; itemDto.Name = item.Name; itemDto.ParentCategory = item.ParentCategory.MapToDto(); itemDto.ChildCategories = item.ChildCategories.Select(x => x.MapToDto()); return itemDto; }
      
      





また、非常に難しいケースでは、関数フィールドが属性に追加され、マッピングを生成するときに、このフィールドのテキストがコードに挿入されました。 [MapIgnore]属性も追加されました。



  [Map(Function="itemDto.Status = item.Status.ToString()") ] public string Status { get; set; }
      
      





WPFクライアントアプリケーションに既に存在するViewモデルにDTOオブジェクトをマップする必要があるため、さらに複雑になりました。

Functionフィールドの代わりに2つのFunctionToフィールドとFunctionFromフィールドが導入されたため、両側のカスタムマッピングは1つの属性のみで記述でき、DO-DTOとDTO-ViewModelマッピングは競合しません。

ReplaceRangeを介したObservableRangeCollectionのマッピング



クラスの最後の例
 namespace DataTransferObject { [Map] public class NewsDto { public Guid? NewsId { get; set; } public string Title { get; set; } public string Anounce { get; set; } public string Text { get; set; } public string Status { get; set; } public CategoryDto Category { get; set; } public DateTime Created { get; set; } public string Author { get; set; } public IEnumerable<string> Tags { get; set; } } } namespace DataObjects { [Map] public class News { public Guid NewsId { get; set; } public string Title { get; set; } public string Anounce { get; set; } public string Text { get; set; } [Map(FunctionFrom = "itemDto.Status = item.Status.ToString()", FunctionTo = "item.Status = (DataObjects.Attributes.StatusEnum) System.Enum.Parse(typeof(DataObjects.Attributes.StatusEnum), itemDto.Status)")] public StatusEnum Status { get; set; } public NewsCategory Category { get; set; } public DateTime Created { get; set; } [Map(FunctionFrom = "itemDto.Author = item.Author.Login")] public User Author { get; set; } [Map(Name = "Tags", FunctionFrom = "itemDto.Tags = item.NewsToTags.Select(p => p.Tag.Name)")] public IEnumerable<NewsToTags> NewsToTags { get; set; } } }
      
      





サンプル生成コード
  public static DataTransferObject.NewsDto MapToDto (this DataObjects.News item) { if (item == null) return null; var itemDto = new DataTransferObject.NewsDto (); itemDto.NewsId = item.NewsId; itemDto.Title = item.Title; itemDto.Anounce = item.Anounce; itemDto.Text = item.Text; itemDto.Status = item.Status.ToString(); itemDto.Category = item.Category.MapToDto(); itemDto.Created = item.Created; itemDto.Author = item.Author.Login; itemDto.Tags = item.NewsToTags.Select(p => p.Tag.Name); return itemDto; } public static DataObjects.News MapFromDto (this DataTransferObject.NewsDto itemDto) { if (itemDto == null) return null; var item = new DataObjects.News (); item.NewsId = itemDto.NewsId.HasValue ? itemDto.NewsId.Value : default(System.Guid); item.Title = itemDto.Title; item.Anounce = itemDto.Anounce; item.Text = itemDto.Text; item.Status = (DataObjects.Attributes.StatusEnum) System.Enum.Parse(typeof(DataObjects.Attributes.StatusEnum), itemDto.Status); item.Category = itemDto.Category.MapFromDto(); item.Created = itemDto.Created; return item; } public static DataTransferObject.CategoryDto MapToDto (this DataObjects.NewsCategory item) { if (item == null) return null; var itemDto = new DataTransferObject.CategoryDto (); itemDto.NewsCategoryId = item.NewsCategoryId; itemDto.Name = item.Name; itemDto.ParentCategory = item.ParentCategory.MapToDto(); itemDto.ChildCategories = item.ChildCategories.Select(x => x.MapToDto()); return itemDto; } public static DataObjects.NewsCategory MapFromDto (this DataTransferObject.CategoryDto itemDto) { if (itemDto == null) return null; var item = new DataObjects.NewsCategory (); item.NewsCategoryId = itemDto.NewsCategoryId; item.Name = itemDto.Name; item.ParentCategory = itemDto.ParentCategory.MapFromDto(); if(itemDto.ChildCategories != null) item.ChildCategories.ReplaceRange(itemDto.ChildCategories.Select(x => x.MapFromDto())); return item; }
      
      







使用例



マッピングを使用するには、次のものが必要です。

  1. プロジェクトから2つのテンプレートファイルを取得ますMapHelper.ttおよびVisualStudioHelper.tt
  2. 2つの属性MapとMapIgnoreを作成します。これらをコピーできます。異なるプロジェクトで同じものを使用する必要はありません。主なことは、それらが同じと呼ばれることです。
  3. 独自のt4テンプレートファイルを作成し、テンプレートを追加して、マッピング設定を指定します( )。




設定
  MapHelper.DoProjects.Add("DataObject"); //  ,   DO  MapHelper.DtoProjects.Add("DataTransferObject"); //  ,   DTO  MapHelper.MapExtensionClassName = "MapExtensionsViewModel"; //     ,   . MapHelper.MapAttribute = "Map"; MapHelper.MapIgnoreAttribute = "MapIgnore"; //  ,    ,          . MapHelper.DtoSuffix = "Dto"; MapHelper.DoSuffix = "ViewModel"; //  ,       . MapHelper.DOSkipAttribute = false; MapHelper.DTOSkipAttribute = false; // ,     [Map]      ,      .
      
      





VisualStudioHelper.tt

このファイルは、私がインターネットで長い間見つけたもので、Visual Studioでプロジェクトの構造を操作するための便利な機能が含まれていますが、徐々に補完され、改善されました。

特に、現在のタスクにメソッドが追加されました。



public List GetClassesByAttributeName(string attributeName、string projectName)-属性名によってプロジェクト内のクラスのリストを取得します。



public List GetAttributesAndPropepertiesCollection(CodeElement要素)-フィールドまたはパラメーターの解析値がある場合、クラスまたはメソッドまたはプロパティから属性のリストを取得します。



パブリックbool IsExistSetterInCodeProperty(CodePropertyコードプロパティ)

パブリックbool IsExistGetterInCodeProperty(CodePropertyコードプロパティ)

プロパティにセッターとゲッターが存在するかどうかを確認してください。



マッピングがより簡単で使いやすくなりました。

  var dto = item.MapToDto()
      
      





誰かが重宝してくれたら嬉しいです。 Github



All Articles