← Back to Article List         
Entity vs DTO

Entity vs DTO

Published on 26 Sep 2026     10 min read Web API
Web API

An Entity represents business data inside your application. A DTO defines the data you accept or return through your API.

For example:

  • Product Entity: Contains product data, including internal fields.
  • CreateProductDto: Contains fields the client can submit.
  • ProductResponseDto: Contains fields the client can receive.

DTO stands for Data Transfer Object.

1. What is an Entity?

An Entity is an object with an identity that represents a business concept, such as a Product, Customer, Order, or Article.

In an application using EF Core, entities are usually mapped to database tables.

Example:

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public decimal Price { get; set; }

    public decimal CostPrice { get; set; }

    public bool IsApproved { get; set; }

    public DateTime CreatedAt { get; set; }
}

Each property has an internal purpose:

Property Purpose
Id Identifies the product
Name Product name
Price Selling price
CostPrice Internal purchase cost
IsApproved Whether the product has been approved
CreatedAt When the product was created

An example database row:

Id Name Price CostPrice IsApproved CreatedAt
1 Laptop 55000 42000 true 2026-09-27

An Entity is not necessarily an exact copy of one database table. Entities can also contain business methods and relationships, and EF Core supports more complex mappings.

2. What is a DTO?

A DTO is an object that carries selected data between parts of a system, commonly between a Web API and its client.

A DTO is designed for a particular operation or response.

For example, when creating a product, the client only needs to send:

public class CreateProductDto
{
    public string Name { get; set; } = string.Empty;

    public decimal Price { get; set; }
}

When returning a product, the API can use:

public class ProductResponseDto
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public decimal Price { get; set; }
}

The response DTO does not expose:

CostPrice
IsApproved
CreatedAt

DTOs let you control your API’s data contract independently of your database model.

3. Why do we need DTOs?

A. Prevent exposing internal information

If you return the Entity directly:

return Ok(product);

The response could include every publicly serializable property:

{
  "id": 1,
  "name": "Laptop",
  "price": 55000,
  "costPrice": 42000,
  "isApproved": true,
  "createdAt": "2026-09-27T08:00:00Z"
}

The client can now see your internal purchase cost.

With a response DTO:

{
  "id": 1,
  "name": "Laptop",
  "price": 55000
}

You explicitly choose what leaves the application.

B. Prevent overposting

Overposting happens when a client submits fields it should not be allowed to control, and the server binds and persists those fields.

Consider this unsafe pattern:

[HttpPost]
public async Task<IActionResult> Create(Product product)
{
    _context.Products.Add(product);

    await _context.SaveChangesAsync();

    return Ok(product);
}

A client could send:

{
  "name": "Laptop",
  "price": 55000,
  "costPrice": 1,
  "isApproved": true
}

Because the API accepts the Entity directly, these properties can be populated and saved.

A request DTO limits the writable fields:

public class CreateProductDto
{
    public string Name { get; set; } = string.Empty;

    public decimal Price { get; set; }
}

The API then explicitly maps only the permitted properties.

DTOs are a documented way to reduce overposting risk in ASP.NET Core APIs. They do not replace authorization or business validation. Microsoft Learn

C. Keep responses smaller

A product list may only need:

Id, Name, Price

It does not need descriptions, audit history, supplier information, and every other field.

D. Reduce coupling to database changes

Suppose the Entity property changes from:

Name

to:

ProductName

The API can continue returning Name by changing its mapping:

Name = product.ProductName

Clients can keep using the same response structure.

E. Combine information from multiple entities

A DTO can contain:

public class OrderSummaryDto
{
    public int OrderId { get; set; }

    public string CustomerName { get; set; } = string.Empty;

    public decimal TotalAmount { get; set; }
}

These values may come from Order, Customer, and OrderItem entities.

A DTO does not need to match a database table.

4. Entity vs DTO — key differences

Aspect Entity DTO
Main purpose Represent business state and identity Transfer data for a specific operation
Typical usage Domain logic and persistence API requests and responses
Database mapping Often mapped using EF Core Usually not mapped
Properties Fields needed by the business model Fields needed by the client or operation
Business behavior May contain business methods Usually contains data and validation metadata
Relationships May have navigation properties Usually shaped into deliberate nested or flat data
EF Core tracking Can be tracked when queried A plain DTO projection is not tracked
Changes Follow business and persistence needs Follow the API contract
Quantity One entity per business concept is common Several DTOs may represent the same entity

5. Common DTO categories

These are naming conventions, not special C# language features.

DTO Purpose Example fields
CreateProductDto Create a product Name, Price
UpdateProductDto Change permitted fields Name, Price
ProductListDto Display a product list Id, Name, Price
ProductDetailsDto Display additional details Id, Name, Price, Description
ProductResponseDto Return a product response Fields selected for that response

Separate DTOs are useful when the operations have different rules. You do not need a new DTO for every method if the contract is genuinely the same.

6. Simple Web API example

This example shows:

  • An Entity stored through EF Core.
  • A request DTO with validation.
  • A response DTO.
  • Mapping in both directions.
  • POST and GET endpoints.

It assumes your Web API already has EF Core SQL Server configured and a database schema created.

Product Entity

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public decimal Price { get; set; }

    public decimal CostPrice { get; set; }

    public bool IsApproved { get; set; }

    public DateTime CreatedAt { get; set; }
}

Create request DTO

using System.ComponentModel.DataAnnotations;

public class CreateProductDto
{
    [Required]
    [StringLength(100)]
    public string Name { get; set; } = string.Empty;

    [Range(typeof(decimal), "0.01", "1000000")]
    public decimal Price { get; set; }
}

Validation rules:

  • Name is required.
  • Name cannot exceed 100 characters.
  • Price must be between 0.01 and 1000000.

With the standard [ApiController] behavior, invalid model data automatically produces a 400 Bad Request response. Microsoft Learn

Response DTO

public class ProductResponseDto
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public decimal Price { get; set; }
}

Database context

using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }

    public DbSet<Product> Products => Set<Product>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>(entity =>
        {
            entity.Property(p => p.Name)
                .HasMaxLength(100)
                .IsRequired();

            entity.Property(p => p.Price)
                .HasPrecision(18, 2);

            entity.Property(p => p.CostPrice)
                .HasPrecision(18, 2);
        });
    }
}

Notice that EF Core stores the Entity:

DbSet<Product>

We do not add DbSet<CreateProductDto> or DbSet<ProductResponseDto>.

Controller

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly AppDbContext _context;

    public ProductsController(AppDbContext context)
    {
        _context = context;
    }

    [HttpPost]
    public async Task<ActionResult<ProductResponseDto>> Create(
        CreateProductDto request,
        CancellationToken cancellationToken)
    {
        // Request DTO -> Entity
        var product = new Product
        {
            Name = request.Name,
            Price = request.Price,

            // These values are controlled by the server.
            IsApproved = false,
            CreatedAt = DateTime.UtcNow

            // CostPrice is left at 0 in this demonstration.
            // A separate internal operation would set it.
        };

        _context.Products.Add(product);

        await _context.SaveChangesAsync(cancellationToken);

        // Entity -> Response DTO
        var response = new ProductResponseDto
        {
            Id = product.Id,
            Name = product.Name,
            Price = product.Price
        };

        return CreatedAtAction(
            nameof(GetById),
            new { id = product.Id },
            response);
    }

    [HttpGet("{id:int}")]
    public async Task<ActionResult<ProductResponseDto>> GetById(
        int id,
        CancellationToken cancellationToken)
    {
        // Project directly into the response DTO.
        var response = await _context.Products
            .Where(p => p.Id == id)
            .Select(p => new ProductResponseDto
            {
                Id = p.Id,
                Name = p.Name,
                Price = p.Price
            })
            .FirstOrDefaultAsync(cancellationToken);

        if (response is null)
        {
            return NotFound();
        }

        return Ok(response);
    }
}

The sample focuses on DTO mapping. In a real application, product creation and access rules also require authorization.

7. Understand the POST flow

Send:

POST /api/products
Content-Type: application/json
{
  "name": "Laptop",
  "price": 55000
}

The processing steps are:

  1. ASP.NET Core deserializes the JSON into CreateProductDto.
  2. Request validation runs.
  3. The controller copies permitted fields into a new Product.
  4. Server-controlled fields receive server values.
  5. EF Core saves the Entity and populates its generated Id.
  6. The controller creates a ProductResponseDto.
  7. The API returns 201 Created, including a Location header.

Response:

{
  "id": 1,
  "name": "Laptop",
  "price": 55000
}

The DTO itself is not saved. The mapped Entity is saved.

If the client adds "isApproved": true, that value has no matching property in CreateProductDto and cannot affect our explicit mapping. By default, unknown JSON properties are ignored; rejecting unknown properties requires separate configuration.

8. What is mapping?

Mapping means copying or transforming values between objects.

Request DTO to Entity:

var product = new Product
{
    Name = request.Name,
    Price = request.Price
};

Entity to response DTO:

var response = new ProductResponseDto
{
    Id = product.Id,
    Name = product.Name,
    Price = product.Price
};

This is manual mapping.

Mapping libraries can reduce repetitive code, but they are optional. Explicit mapping is easy to understand and makes permitted input fields visible.

9. Why use Select() for GET requests?

Consider these two approaches.

Load the Entity, then map:

var product = await _context.Products
    .FirstOrDefaultAsync(p => p.Id == id);

This normally retrieves the Entity’s mapped scalar columns, including internal fields.

Project directly to a DTO:

var response = await _context.Products
    .Where(p => p.Id == id)
    .Select(p => new ProductResponseDto
    {
        Id = p.Id,
        Name = p.Name,
        Price = p.Price
    })
    .FirstOrDefaultAsync();

For this query, EF Core can select only the requested columns.

Also, the result contains no Entity instance, so there is no Entity to track. AsNoTracking() is not required for this scalar-only DTO projection. If a DTO contains an actual Entity object, that Entity can still be tracked. Microsoft Learn

Using a DTO does not automatically optimize a query. Projecting before loading the data is what allows the database query to fetch fewer columns.

10. Where should validation go?

Validation type Typical location Example
Request format and shape Request DTO Name required, maximum length
Business rules Domain or application layer Only approved products may be sold
Access permissions Authorization and application logic Only purchasing staff can change cost
Database integrity Database constraints and EF configuration Required column, unique index, foreign key

DTO validation does not replace database constraints or business rules.

For example, [StringLength(100)] on a DTO validates incoming requests. It does not configure the Entity’s database column length.

11. Where do they belong in Clean Architecture?

A common arrangement is:

Layer Typical contents
Domain Entities and business rules
Application Use cases and application DTOs
Infrastructure EF Core context and Entity mappings
Web API Controllers and HTTP-specific request/response DTOs

DTO placement depends on who owns the contract. Application DTOs can live in Application; HTTP-specific contracts can live in the API project.

The Domain layer should not depend on API DTOs.

12. Advantages and trade-offs

Advantages:

  • Controls which fields clients can read and write.
  • Reduces accidental exposure of internal information.
  • Helps prevent overposting.
  • Supports different list, detail, create, and update contracts.
  • Reduces unnecessary response data.
  • Separates API contracts from persistence changes.
  • Avoids accidentally serializing large Entity relationship graphs.

Trade-offs:

  • Requires additional classes.
  • Requires mapping code.
  • Mapping must be maintained as requirements change.

13. Key points for interviews

  • Entity: Represents business state and identity.
  • DTO: Defines data transferred for a particular operation.
  • Entities are commonly persisted; DTOs are commonly serialized.
  • One Entity can have multiple DTOs.
  • DTOs can combine data from multiple entities.
  • A DTO may include an Id; it is not restricted to non-key fields.
  • DTOs can be classes or records.
  • Request and response DTOs often have different properties.
  • DTOs do not automatically provide authorization.
  • Changing a DTO does not automatically change the database.
  • Use explicit input mapping and server-controlled values for sensitive fields.
  • For read endpoints, project into DTOs before materializing the query when practical.