Skip to content

Entities as DTOs

Leonardo Porro edited this page Sep 13, 2023 · 10 revisions

EntityFramework dropped self-tracking objects long ago, and replaced it by the ChangeTracker. Change tracking allows to use plain and clean entities (POCO) which doesn't need to inherit from a specific base class or define anything related to the underlying database.

This often leads devs into trying to use DB entities everywhere in the application, from the frontend to the data access layer, avoiding the tedious task of creating DTOs and doing the mapping.

Sometimes, these nice and clean entities work as DTOs, but as the application becomes more and more complex, the developer will realize that most of the times, entities and DTOs are totally different beasts.

Here there are some examples of fields that shouldn't be exposed because they are tied to a technology, difficult to serialize, a security issue, ugly or just not needed:

  • A field whose type is specific to a technology, e.g.: DbGeography
  • Fields specific to SQL, e.g.: Foreign Keys
  • Fields with sensitive data, e.g.: Password
  • Fields with sensitive or unneded data, e.g.: CreatedTimestamp
  • Internal behavior fields, e.g.: ConcurrencyToken
  • Database generated fields, e.g.: Id, (not needed for "Creation" use case)
  • Any extra field that is not needed for the current use case and just wastes memory and bandwidth.

As most devs probably know, Swagger generates documentation for every field it finds using the aspnet api explorer, so that, all of those unwanted fields will be displayed unless proper DTOs are created.

Suggestion 1: Create DTOs!

Developers are often afraid of creating DTOs because the amount of code they need to write the mapping and set the right states or the high number of classes they need add.

The goal of this library is to simplify that task so creating DTOs is less painful.

A CQRS pattern works well with the mapper. For example, if there is a UserEntity { Id, Name }, then it makes sense to have CreateUserCommand { Name }, and UpdateUserCommand { Id, Name }. Then, the library can be used to map the Command fields directly to the matching Entity fields.

Suggestion 2: Use good naming

[Entity name] + DTO suffix (e.g. UserDTO) is usually a very bad name. In most of the cases, DTO suffix should be completely avoided.

DTO names should describe the purpose of it, such as CreateUserInput for CreateUser operation, or CurrentUser for GetCurrentUser operation.

Generic names like UserDTO make difficult to find the classes when developing, and also prone to be reused when it shouldn't.

Probably, CurrentUser should have just id, name and maybe roles fields, while CreateUserInput may have several other fields, like address, phone, dateOfBirth, userType, etc.

That's why, for example, would be a very bad idea to reuse UserDTO for GetCurrentUser and CreateUserMethod operations.