4. Basic Data Layer
This iteration we’ll set up a basic data layer comprising: a single data model, a DbContext and a PostgreSQL database running in Docker.
Maps to
iteration_01in the Run1API Repo
About this chapter
Structuring your data for APIs:
- Domain models: Entity classes with relationships and validation
- DTOs (Data Transfer Objects): Contracts for API communication
- Why separation matters: Security, versioning, and over-posting prevention
- C# features: Required members, nullable reference types, navigation properties
- Best practices: Conventions and proper property naming
Learning outcomes:
- Create domain models with relationships and constraints
- Understand the DTO pattern and why it’s essential for APIs
- Design DTOs for create, read, and update operations
- Apply C# naming conventions and validation attributes
3.1 Creating Domain Models
namespace CommandAPI.Models;
public class Platform
{
[Key]
public int Id { get; set; }
[Required]
public required string PlatformName { get; set; }
// Navigation property
public ICollection<Command> Commands { get; set; } = new List<Command>();
public string? CreatedBy { get; set; }
}
- Required Members (C# 11+):
requiredkeyword enforces initialization - Nullable Reference Types:
string?vsstring - Navigation Properties: EF Core uses these for relationships
- Collection Initialization: Best practice to avoid null reference issues
3.2 Understanding DTOs and Why We Need Them
- DTO = Data Transfer Object: Contracts for API communication
- Why Not Expose Models Directly?:
- Security: Hide sensitive fields (passwords, internal IDs)
- Versioning: API contract independent of database schema
- Over-posting: Prevent clients from setting unauthorized fields
- Performance: Return only needed data
- Circular References: Avoid JSON serialization issues
3.3 CORRECTION: Proper C# Naming Conventions
// ❌ WRONG (current code has this issue)
public class AppDbContext : DbContext
{
public DbSet<Platform> platforms { get; set; } // camelCase
public DbSet<Command> commands { get; set; }
}
// ✅ CORRECT
public class AppDbContext : DbContext
{
public DbSet<Platform> Platforms { get; set; } // PascalCase
public DbSet<Command> Commands { get; set; }
public DbSet<KeyRegistration> KeyRegistrations { get; set; }
public DbSet<BulkJobResult> BulkJobResults { get; set; }
}
- C# Naming Conventions:
- Classes, Properties, Methods:
PascalCase - Local variables, parameters:
camelCase - Private fields:
_camelCase(with underscore) - Constants:
UPPER_SNAKE_CASEorPascalCase
- Classes, Properties, Methods:
3.4 Data Annotations for Basic Validation
public class PlatformMutateDto
{
[Required(ErrorMessage = "Platform name is required")]
[MaxLength(100, ErrorMessage = "Platform name cannot exceed 100 characters")]
[MinLength(2, ErrorMessage = "Platform name must be at least 2 characters")]public required string PlatformName { get; init; }
}
public class CommandMutateDto
{
[Required]
[MaxLength(250)]
public required string HowTo { get; init; }
[Required]
public required string CommandLine { get; init; }
[Range(1, int.MaxValue, ErrorMessage = "Valid Platform ID required")]
public int PlatformId { get; set; }
}
- Common Annotations:
[Required]- non-null, non-empty[MaxLength(n)]- string/array length[Range(min, max)]- numeric bounds[EmailAddress],[Url],[Phone]- format validation[RegularExpression(pattern)]- custom patterns
3.5 The Separation of Concerns Principle
┌─────────────────┐ ┌──────────────┐ ┌────────────────┐
│ Controller │────▶│ Repository │────▶│ DbContext │
│ (HTTP Logic) │ │ (Data Logic) │ │ (EF Core) │
└─────────────────┘ └──────────────┘ └────────────────┘
│ │
▼ ▼
┌─────────┐ ┌──────────┐
│ DTO │ │ Models │
└─────────┘ └──────────┘
│ │
└────────────────┬─────────────────────────────┘
▼
┌────────────┐
│ AutoMapper │
└────────────┘
- Layers Explained:
- Controller: Handles HTTP, validates input, returns responses
- Repository: Data access logic, queries, business rules
- DbContext: EF Core abstraction over database
- Models: Database entities
- DTOs: API contracts
- AutoMapper: Transforms between Models and DTOs