EF Core: Modeling Data

This document guides you through the process of modeling your application's data using Entity Framework Core (EF Core). EF Core allows you to define your domain model using C# classes and then have EF Core map these classes to database tables.

Core Concepts of EF Core Modeling

EF Core uses conventions and explicit configuration to understand how your .NET objects should be represented in a relational database. The primary components of EF Core modeling include:

Convention-Based Modeling

EF Core follows a set of conventions to infer the database schema from your model. By adhering to these conventions, you can often create a functional model with minimal explicit configuration.

Key conventions include:

Example: Convention-Based Model

C#

public class Blog
{
    public int Id { get; set; }
    public string Url { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; } // Conventionally discovered foreign key
    public Blog Blog { get; set; } // Conventionally discovered navigation property
}

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    // Configuration for database connection would go here
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=BloggingEFCore;Trusted_Connection=True;");
    }
}
            

Data Annotations

For scenarios where conventions are not sufficient or you want to explicitly define aspects of your model, you can use data annotations. These are attributes placed on your entity classes and properties.

Common data annotations include:

Example: Model with Data Annotations

C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

public class Product
{
    [Key] // Explicitly mark Id as the primary key
    public int ProductId { get; set; }

    [Required] // Ensure Product Name is not null
    [StringLength(100)] // Limit the length of the string
    public string ProductName { get; set; }

    [Column("ProductPrice")] // Map to a column named "ProductPrice"
    public decimal Price { get; set; }

    public int CategoryId { get; set; }
    [ForeignKey("CategoryId")] // Explicitly define the foreign key relationship
    public Category Category { get; set; }
}

public class Category
{
    [Key]
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Product> Products { get; set; }
}
            

Fluent API

The Fluent API provides the most powerful and flexible way to configure your EF Core model. It is configured within the OnModelCreating method of your DbContext.

You can use the Fluent API to configure:

Note: When both data annotations and the Fluent API are used to configure the same aspect of the model, the Fluent API takes precedence.

Example: Model with Fluent API

C#

using Microsoft.EntityFrameworkCore;

public class SchoolContext : DbContext
{
    public DbSet<Student> Students { get; set; }
    public DbSet<Course> Courses { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=SchoolEFCore;Trusted_Connection=True;");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Configure Student entity
        modelBuilder.Entity<Student>(entity =>
        {
            entity.HasKey(s => s.StudentId); // Explicitly define primary key
            entity.Property(s => s.FirstName).IsRequired().HasMaxLength(50);
            entity.Property(s => s.LastName).IsRequired().HasMaxLength(50);
            entity.ToTable("Students"); // Map to a table named "Students"
        });

        // Configure Course entity
        modelBuilder.Entity<Course>(entity =>
        {
            entity.HasKey(c => c.CourseId);
            entity.Property(c => c.Title).IsRequired().HasMaxLength(100);
            entity.Property(c => c.Credits).HasColumnType("decimal(3, 1)"); // Specific decimal type
            entity.ToTable("Courses");
        });

        // Configure relationship: One Course has many Students
        modelBuilder.Entity<Student>()
            .HasOne(s => s.EnrolledCourse)
            .WithMany(c => c.Students)
            .HasForeignKey(s => s.CourseId); // Foreign key property
    }
}

public class Student
{
    public int StudentId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int CourseId { get; set; } // Foreign key property
    public Course EnrolledCourse { get; set; } // Navigation property
}

public class Course
{
    public int CourseId { get; set; }
    public string Title { get; set; }
    public decimal Credits { get; set; }
    public virtual ICollection<Student> Students { get; set; }
}
            

Choosing the Right Approach

The best approach for modeling your data depends on the complexity of your domain and your preference:

Often, a combination of conventions and Fluent API (or conventions and data annotations) is used to achieve the desired model.

Tip: Start with conventions, then add data annotations or Fluent API configurations as needed. This iterative approach helps manage complexity.

Summary

Entity Framework Core provides powerful mechanisms for modeling your application's data. By understanding conventions, data annotations, and the Fluent API, you can effectively map your .NET objects to your database schema, enabling robust data access for your applications.