在大型 .NET 項目中管理 DTO 和映射 (Managing DTOs and mapping in large .NET project)


問題描述

在大型 .NET 項目中管理 DTO 和映射 (Managing DTOs and mapping in large .NET project)

My team and I are building a large .NET WinForms applications. The application uses various "Services" to get data to and from our database. Each "service" lives in its own solution, and handles a specific type of data. So, for example, our "ContactsService" manages retrieving / saving contacts to our database.

Typically, we have been building DTOs for each service. So, we might have a "ContactDTO" that has simple string properties for each piece of data on the contact. Now, we also have a business layer "Contact" class that has the exact same properties, and perhaps a few extra methods with some business logic. On top of that, the "ContactsService" has its own Contact class, which is hydrated from the ContactDTO.

It has become a huge pain to manage all of our DTOs and mapping. Currently, sending a contact to be stored in the database looks like this:

  • Map client Contact to ContactDTO
  • Map ContactDTO to service Contact
  • Save Contact
  • Map service Contact to ContactDTO
  • Map ContactDTO to client Contact

This just feels crappy. If we add a property to our client Contact class, we have to add the property and mappings in 3-4 places.

What are we doing wrong and how can we make our lives easier? Would it be simpler to use something like Json.NET and have JSON DTOs? We checked out AutoMapper, but some team members thought it was too complicated.


參考解法

方法 1:

I've run into this a lot, but in my case I've chosen to accept it because the decision to use DTOs was an intentional one - I want my service, client, proxy, and contract assemblies to be cleanly separated.

When using WCF, the layout I prefer is something like this (using your Contact example):

  • Client assembly
    • Contact (has client-y characteristics)
  • Shared Contracts assembly
    • Contact (the agreed-upon common DTO)
  • Proxy assembly
    • Uses the Contact from the shared Contracts assembly
  • Service assembly
    • Contact (has service-y business logic characteristics, e.g. this may be the type exposed by an ORM layer like Entity Framework)

I'm now sharing the contracts between the service and the client.  If I ever need to version them independently, then I can just make a copy of the shared Contracts assembly and re-target the Proxy assembly to use the copy, and then modify the two Contracts assemblies independently.  But in most cases that I've worked in, I own both the client and the service, so it's convenient to share the Contracts assembly between the two.

That's the only optimization that I can think of when you make the architectural decision to isolate your components with DTOs, at least without using code generation tools (I don't know of any good ones, but haven't had to look into them).

Edit: When not using WCF, you obviously won't need the 'Proxy' assembly.

方法 2:

AutoMapper isn't particularly complicated.  If you follow the conventions, e.g. Contact.Address1 becomes ContactAddress1 on the DTO you won't have to write much code apart from the call to Mapper.Map.  Alternatively you can use code generation but it will still be tricky to manage change.

Can I ask why you feel the need to have both a ContactDTO and a service contact?  Can you not just pass the service Contact around?  I known it's not best practice but it might save you from RSI 

EDIT: Disregard my last point - for some reason I thought you were mapping the service Contact to a database entity such as NHibernate/Entity Framework

方法 3:

Some things to speed up the process to consider:

I wrote a code generators that you could pick the table(s) and columns from the database and have it generate a C# DTO.  This saves a lot of needless typing and can generate DTOs a lot quicker.  A little time up front, but when you have a table with 20 columns you need to map, it helps.

I also wrote code generators to map the DTOs to stored procedure for save, delete, and query operations. Again a time saver because a lot of them end up being very similar.  If you have of lot of tedious "grunt" code being written, consider a code generator to do it.

Use an entity framework or an ORM mapper for the back end.  This can save a lot of time but you have to invest in the ORM knowledge and learn how it works.

Create a common set of DTOs that are used from client to database.  This may not be practical in some situations when you need proxies or smaller DTOs for the client, but you can try to have some common DTOs that are passed from client all the way back to the server.

Remove some of the layers.  I know this sounds a bit of an anti pattern, but in some cases the onion doesn't have to be so thick.

(by TaylorOtwellLars Kemmannuser528573Jon Raynor)

參考文件

  1. Managing DTOs and mapping in large .NET project (CC BY-SA 3.0/4.0)

#mapping #.net #dto






相關問題

Hibernate 單向父/子關係 - delete() 對子表執行更新而不是刪除 (Hibernate Unidirectional Parent/Child relationship - delete() performs update on child table instead of delete)

TIGER shapefile - 使用和解釋 (TIGER shapefiles - using and interpreting)

查找位置的城市和郵政編碼 (Finding City and Zip Code for a Location)

如何使 Vim 插件 NERDTree 的 <CR> 表現得更像 `go`? (How to make Vim plugin NERDTree's <CR> behave more like `go`?)

在 NHibernate 上的多對多關係上添加自定義列 (Add custom columns on many to many relationship on NHibernate)

在大型 .NET 項目中管理 DTO 和映射 (Managing DTOs and mapping in large .NET project)

使用 Fluent NHibernate 進行繼承映射 (Inheritance Mapping with Fluent NHibernate)

Biztalk映射問題,請想法 (Biztalk Mapping Problem, Ideas please)

BizTalk 數據庫查找 functoid 固定條件 (BizTalk Database Lookup functoid fixed condition)

EntityFramework FluentAPI 映射問題 (EntityFramework FluentAPI mapping issue)

將外鍵添加到現有數據庫 (Adding foreign keys to an already existing database)

如何重命名對像數組中對象的所有鍵? (How does one rename all of an object's keys within an array of objects?)







留言討論