Designing Databases from Your C# Classes
With Code First in Entity Framework (EF), you start with C# classes to define your data model, and EF generates the corresponding database schema. This approach is ideal for domain-driven design, agile development, or when you want full control over your code first.
Code First supports defining entities, properties, relationships, constraints, and navigation properties – all in code, without an initial database.
๐น What Is Code First?
Code First workflow:
- Define C# classes representing your entities (tables).
- Define relationships using navigation properties and data annotations or Fluent API.
- EF generates the database schema based on your model.
- Migrations help evolve the database as your models change.
โ Offers a code-centric approach to database design while keeping EF in charge of schema creation.
๐น Defining Basic Models
Example: Employee and Department entities:
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
// Navigation property: one-to-many
public virtual ICollection<Employee> Employees { get; set; } = new List<Employee>();
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Salary { get; set; }
// Foreign key
public int DepartmentId { get; set; }
// Navigation property
public virtual Department Department { get; set; }
}
โ
virtual keyword enables lazy loading.
โ
Collections represent one-to-many relationships.
๐น Defining Relationships
1. One-to-Many
- A Department can have many Employees.
- Employee has a foreign key
DepartmentIdand a navigation propertyDepartment. - Department has an
ICollection<Employee>property.
2. Many-to-Many
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Course> Courses { get; set; } = new List<Course>();
}
public class Course
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Student> Students { get; set; } = new List<Student>();
}
โ EF Core automatically generates a join table for many-to-many relationships.
๐น Data Annotations vs Fluent API
Data Annotations (Attributes)
public class Product
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
}
[Required]โ NOT NULL[MaxLength]โ column length[Column(TypeName = "decimal(18,2)")]โ data type in database
Fluent API (In DbContext)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasOne(e => e.Department)
.WithMany(d => d.Employees)
.HasForeignKey(e => e.DepartmentId);
}
โ Fluent API gives more control over relationships, keys, table names, and constraints.
๐น Creating the Database
- Create a DbContext:
public class CompanyContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<Department> Departments { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlServer("Server=.;Database=CompanyDB;Trusted_Connection=True;");
}
- Use EF Core Migrations:
Add-Migration InitialCreate
Update-Database
โ EF generates tables, relationships, and foreign keys automatically.
๐น Querying Relationships
using (var context = new CompanyContext())
{
var employees = context.Employees
.Include(e => e.Department)
.Where(e => e.Salary > 50000)
.OrderByDescending(e => e.Salary)
.ToList();
foreach (var e in employees)
Console.WriteLine($"{e.Name} ({e.Department.Name}) - ยฃ{e.Salary}");
}
Output:
Clara (IT) - ยฃ65000
Alice (IT) - ยฃ60000
โ
Include eager loads related entities.
๐น Advanced Relationships
Composite Keys
modelBuilder.Entity<OrderDetail>()
.HasKey(od => new { od.OrderId, od.ProductId });
One-to-One
modelBuilder.Entity<User>()
.HasOne(u => u.UserProfile)
.WithOne(p => p.User)
.HasForeignKey<UserProfile>(p => p.UserId);
โ Code First supports all EF relationship types.
๐น Best Practices
- Prefer Fluent API for complex relationships.
- Use Data Annotations for simple, self-explanatory constraints.
- Initialize collections to avoid null references.
- Use migrations for incremental database updates.
- Keep DbContext lean and focused on your models.
๐งช Challenge Task
- Create models for Department, Employee, and Project.
- Define relationships:
- Department โ Employees (one-to-many)
- Employee โ Projects (many-to-many)
- Configure a DbContext and generate the database.
- Write a LINQ query to return:
- Employee Name
- Department Name
- Number of Projects assigned
Expected Output:
Alice (IT) - 3 projects
Bob (HR) - 1 project
Clara (IT) - 2 projects
๐จโ๐ป Want More?
Our C# Courses cover:
- Code First modeling and migrations
- Relationships: one-to-one, one-to-many, many-to-many
- Data annotations vs Fluent API
- LINQ queries, projections, grouping, and joins
- Best practices for maintainable, domain-driven design
Master Code First to design your database in code, control every aspect of your schema, and write type-safe, maintainable C# applications.