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.

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:

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.

Note: The Fluent API overrides conventions and data annotations when there's a conflict.

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:

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:

Tip: Always review generated migration scripts before applying them to production databases.