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:
Nameis required.Namecannot exceed 100 characters.Pricemust be between0.01and1000000.
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:
- ASP.NET Core deserializes the JSON into
CreateProductDto. - Request validation runs.
- The controller copies permitted fields into a new
Product. - Server-controlled fields receive server values.
- EF Core saves the Entity and populates its generated
Id. - The controller creates a
ProductResponseDto. - The API returns 201 Created, including a
Locationheader.
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.