Modeling Entity Framework Core
This section delves into the various ways you can model your data using Entity Framework Core (EF Core), a powerful object-relational mapper (ORM) for .NET.
EF Core Conventions
EF Core uses a set of default conventions to infer how your .NET classes map to your database schema. Understanding these conventions can significantly reduce the amount of explicit configuration you need.
- Primary Keys: Properties named
Id
or{ClassName}Id
are automatically recognized as primary keys. - Navigation Properties: Collection properties (e.g.,
List<T>
) and reference properties (e.g.,T
) are treated as relationships. - Property Naming: CLR property names are mapped to column names (often with casing adjustments).
Data Annotations
You can use data annotations, which are attributes applied to your model classes, to provide explicit configuration. This is useful for simple customizations.
Common Data Annotations:
[Key]
: Designates a property as the primary key.[Required]
: Makes a property non-nullable in the database.[StringLength(n)]
: Specifies the maximum length of a string column.[MaxLength(n)]
: Similar to[StringLength(n)]
, but can also apply to byte arrays.[Column(TypeName = "sql_type")]
: Specifies the database type for a column.[Table("table_name")]
: Specifies the name of the database table.[ForeignKey("OtherEntityId")]
: Configures a foreign key property.
Example:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class Product
{
[Key]
public int ProductId { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public Category Category { get; set; }
}
Fluent API
For more complex configuration, the Fluent API, configured in the OnModelCreating
method of your DbContext
, offers greater flexibility. It allows you to define your entire model in code.
Example:
using Microsoft.EntityFrameworkCore;
public class MyDbContext : 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>()
.ToTable("InventoryProducts");
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId);
}
}
Configuring Relationships
EF Core supports one-to-one, one-to-many, and many-to-many relationships. You can configure these using data annotations or the Fluent API.
Many-to-Many Example (using Fluent API):
To create a many-to-many relationship, you typically need a linking (or join) table. EF Core can automatically create this for you.
// Assume you have these entities:
public class Post { public int PostId { get; set; } public string Title { get; set; } public ICollection<Tag> Tags { get; set; } }
public class Tag { public int TagId { get; set; } public string Name { get; set; } public ICollection<Post> Posts { get; set; } }
// In DbContext.OnModelCreating:
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(t => t.Posts)
.UsingEntity(joinEntity => joinEntity.ToTable("PostTags"));
Handling Inheritance
EF Core supports mapping class inheritance hierarchies to the database. It can use different strategies:
- Table-per-Hierarchy (TPH): All entities in the hierarchy are stored in a single table, with a discriminator column to distinguish them. This is the default.
- Table-per-Type (TPT): Each entity in the hierarchy gets its own table.
- Table-per-Concrete-Type (TPC): Only concrete types get their own table, abstract types do not.
Example (TPH):
// Base class
public abstract class Shape { public int Id { get; set; } public string Name { get; set; } }
// Derived classes
public class Circle : Shape { public double Radius { get; set; } }
public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } }
// In DbContext.OnModelCreating:
modelBuilder.Entity<Shape>()
.HasDiscriminator<string>("ShapeType") // Discriminator column name
.HasValue<Circle>("Circle")
.HasValue<Rectangle>("Rectangle");
Working with Migrations
EF Core Migrations allow you to evolve your database schema over time as your model changes. They generate SQL scripts to apply these changes.
Key Commands:
Add-Migration InitialCreate
: Creates a new migration.Update-Database
: Applies pending migrations to the database.Remove-Migration
: Removes the last migration.Script-Migration
: Generates a SQL script for migrations.