Entity Framework Core Data Definition
This document provides a comprehensive guide to defining your data models using Entity Framework Core (EF Core). EF Core allows you to map your .NET objects to relational database tables, simplifying data access and management.
Core Concepts
Entities
Entities are .NET classes that represent tables in your database. Each property of an entity class typically maps to a column in the corresponding table.
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
Entity Types
EF Core discovers entity types by scanning your DbContext for DbSet<T>
properties. You can also explicitly configure entity types using the ModelBuilder API.
DbSet
The DbSet<TEntity>
property in your DbContext
represents a collection of all entities of a given type in the context, or that can be queried from the database.
public class ApplicationDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
// ... other configurations
}
Mapping to Database Tables
Convention-Based Mapping
EF Core uses conventions to map your entity types to database tables and your properties to columns. By default:
- Entity classes are mapped to tables with names matching the
DbSet<TEntity>
property name (or the entity class name if noDbSet
is used). - Primary key properties named
Id
or<EntityName>Id
are automatically recognized. - Other properties are mapped to columns with the same name.
Data Annotations
You can use data annotations from the System.ComponentModel.DataAnnotations
namespace to configure your entity models directly within your classes.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class Product
{
[Key]
public int ProductId { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
public int CategoryId { get; set; }
[ForeignKey("CategoryId")]
public Category Category { get; set; }
}
Fluent API
For more complex configurations or to avoid cluttering your entity classes, you can use the Fluent API in your DbContext
's OnModelCreating
method.
public class ApplicationDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasKey(p => p.ProductId);
modelBuilder.Entity<Product>()
.Property(p => p.Name)
.IsRequired()
.HasMaxLength(100);
modelBuilder.Entity<Product>()
.Property(p => p.Price)
.HasColumnType("decimal(18,2)");
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId);
}
}
Entity Relationships
One-to-Many
A common relationship where one entity can be associated with multiple other entities.
// Category class
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
One-to-One
Each entity in one set corresponds to exactly one entity in another set.
// User class
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
public virtual UserProfile Profile { get; set; }
}
// UserProfile class
public class UserProfile
{
public int UserId { get; set; } // Foreign key also serves as primary key
public string FullName { get; set; }
public DateTime DateOfBirth { get; set; }
public virtual User User { get; set; }
}
Configuration in OnModelCreating
:
modelBuilder.Entity<User>()
.HasOne(u => u.Profile)
.WithOne(p => p.User)
.HasForeignKey<UserProfile>(p => p.UserId);
Many-to-Many
Entities in one set can be associated with multiple entities in another set, and vice versa. This is typically implemented using a joining table.
// Post class
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
// Tag class
public class Tag
{
public int TagId { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
EF Core can automatically create the joining table (e.g., `PostTag`) with the correct foreign keys when using conventions or explicit mapping in OnModelCreating
.
Table and Column Naming
Table Names
Default: DbSet<Product> Products
maps to a table named "Products".
Fluent API override:
modelBuilder.Entity<Product>().ToTable("ProductCatalog");
Column Names
Default: Property Name
maps to a column named "Name".
Data Annotation override:
[Column("ProductName")]
public string Name { get; set; }
Fluent API override:
modelBuilder.Entity<Product>().Property(p => p.Name).HasColumnName("ProductName");
Key Configuration
Primary Keys
Configured automatically for properties named Id
or <EntityName>Id
.
Fluent API configuration:
modelBuilder.Entity<Product>().HasKey(p => p.ProductId);
Foreign Keys
Configured automatically based on naming conventions and navigation properties.
Fluent API configuration (already shown in relationships examples).
Value Generation
Identity (Auto-increment)
By default, integer primary keys are configured as identity columns (auto-incrementing).
Fluent API configuration:
modelBuilder.Entity<Product>()
.Property(p => p.ProductId)
.ValueGeneratedOnAdd();
Computed Values
For columns whose values are generated by the database (e.g., using triggers or default constraints).
modelBuilder.Entity<Product>()
.Property(p => p.RowVersion)
.IsRowVersion(); // Example for concurrency tokens
modelBuilder.Entity<Product>()
.Property(p => p.CreatedAt)
.ValueGeneratedOnAddOrUpdate(); // For audit timestamps
Data Types and Constraints
Column Types
You can explicitly specify the database data type for a column.
// Using Data Annotations
[Column(TypeName = "varchar(50)")]
public string Code { get; set; }
// Using Fluent API
modelBuilder.Entity<Product>().Property(p => p.Code).HasColumnType("varchar(50)");
Required Properties
Ensures that a column cannot contain null values.
// Using Data Annotations
[Required]
public string Name { get; set; }
// Using Fluent API
modelBuilder.Entity<Product>().Property(p => p.Name).IsRequired();
Max Length
Sets the maximum length for string or byte array columns.
// Using Data Annotations
[MaxLength(255)]
public string Description { get; set; }
// Using Fluent API
modelBuilder.Entity<Product>().Property(p => p.Description).HasMaxLength(255);
Uniqueness
Ensures that all values in a column are unique.
modelBuilder.Entity<Product>().HasIndex(p => p.Sku).IsUnique();