Modeling Data with Entity Framework Core
This section guides you through the process of defining your domain models and configuring how Entity Framework Core (EF Core) maps them to your database.
Core Concepts
EF Core uses conventions and explicit configuration to understand your domain classes and translate them into database schema. The primary building blocks are:
- Entities: Plain Old CLR Objects (POCOs) that represent data in your application.
- DbSets: Collections of entities available through your
DbContext
. - DbContext: The main class for interacting with the database. It represents a session with the database and allows you to query and save data.
Conventions
EF Core applies a set of default conventions to infer the database schema from your model. Some common conventions include:
- Public properties with a getter and setter of a specific type are considered properties of the entity.
- Properties named
Id
or<ClassName>Id
are treated as the primary key. - Navigational properties to other entities are treated as foreign key relationships.
Configuring the Model
While conventions are useful, you'll often need to provide explicit configuration to customize your model mapping. This can be done in two main ways:
1. Data Annotations
You can use attributes from the System.ComponentModel.DataAnnotations
namespace to configure your model directly on your entity classes.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class Product
{
[Key] // Marks this property as the primary key
public int ProductId { get; set; }
[Required] // Makes this property non-nullable
[MaxLength(255)] // Sets the maximum length of the string column
public string Name { get; set; }
[Column(TypeName = "decimal(18, 2)")] // Specifies the database column type
public decimal Price { get; set; }
public int CategoryId { get; set; }
[ForeignKey("CategoryId")] // Configures the foreign key relationship
public Category Category { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
2. Fluent API
The Fluent API provides a more powerful and centralized way to configure your model within the OnModelCreating
method of your DbContext
.
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(entity =>
{
entity.HasKey(e => e.ProductId); // Primary key configuration
entity.Property(e => e.Name)
.IsRequired() // Non-nullable
.HasMaxLength(255); // Max length
entity.Property(e => e.Price)
.HasColumnType("decimal(18, 2)"); // Column type
entity.HasOne(p => p.Category) // One-to-many relationship
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId); // Foreign key property
});
modelBuilder.Entity<Category>(entity =>
{
entity.HasKey(e => e.CategoryId);
entity.Property(e => e.Name).IsRequired();
});
}
}
Relationships
EF Core supports mapping common relational database concepts:
- One-to-One: A single instance of entity A is related to a single instance of entity B.
- One-to-Many: A single instance of entity A can be related to multiple instances of entity B.
- Many-to-Many: Multiple instances of entity A can be related to multiple instances of entity B. EF Core automatically creates a linking table for this relationship.
Relationships are typically defined using navigational properties and foreign key properties.
Complex Types and Value Objects
EF Core allows you to model complex data structures that don't have their own primary key by treating them as owned types or value objects. This can help in organizing your domain model without creating separate tables for every component.
Table Splitting
If you have a single entity that logically spans multiple database tables (e.g., for performance or organization), EF Core supports table splitting. This maps multiple tables to a single entity.
Owned Types
Owned types allow you to model complex properties that do not have their own identity. They are owned by another entity and share the same primary key. This is useful for representing address, contact information, etc.
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
public Address ShippingAddress { get; set; } // Owned type
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
}
In the DbContext
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().OwnsOne(c => c.ShippingAddress);
}
Database Generation
Once your model is defined, EF Core can generate the database schema for you using Migrations. This process involves creating migration files that represent changes to your database schema over time.
Next Steps
Continue by exploring how to query data efficiently using EF Core and manage data changes through migrations.