Entity Framework Core: Modeling Data

Tip: Understanding data modeling is fundamental to effectively using Entity Framework Core (EF Core).

Introduction to Data Modeling in EF Core

Entity Framework Core provides a powerful object-relational mapper (ORM) that allows you to work with your database as if you were using regular .NET objects. Data modeling is the process of defining the structure of your data, including entities, their properties, and the relationships between them.

EF Core can infer your data model by inspecting your application's domain classes. This is known as the Code First approach. Alternatively, you can define your model explicitly using the EF Core API, or generate a model from an existing database (Database First).

Entities and Properties

Entities are the .NET classes that represent tables in your database. Each property of an entity class typically maps to a column in that table.

For example, consider a simple `Product` entity:


public class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}
                

In this example, `ProductId`, `Name`, and `Price` will be mapped to columns in a `Products` table.

Convention-Based Mapping

EF Core uses conventions to automatically map your classes and properties to your database schema. Some common conventions include:

  • Public classes are treated as entities.
  • Properties with a public getter and setter are mapped as columns.
  • A property named `Id` or `ClassNameId` is recognized as the primary key.

Relationships Between Entities

Real-world applications often involve relationships between different data entities. EF Core supports common relationship types:

  • One-to-Many: One `Category` can have many `Products`.
  • Many-to-One: Many `Products` can belong to one `Category`.
  • One-to-One: One `User` might have one `UserProfile`.
  • Many-to-Many: Many `Students` can enroll in many `Courses`.

Defining Relationships with Navigation Properties

Navigation properties are used to represent relationships. They allow you to traverse from one entity to another.

Example of a one-to-many relationship between `Category` and `Product`:


public class Category
{
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public ICollection<Product> Products { get; set; } = new List<Product>();
}

public class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    public int CategoryId { get; set; } // Foreign key
    public Category Category { get; set; }
}
                

In this scenario, `Category.Products` is the navigation property from `Category` to `Product`, and `Product.Category` is the navigation property from `Product` to `Category`. The `CategoryId` property in `Product` acts as the foreign key.

Configuring Your Model

While conventions are powerful, you can explicitly configure your model to override conventions or define complex mappings. This is typically done in the `OnModelCreating` method of your `DbContext` class.

Fluent API

The Fluent API allows you to configure your model using a chainable method syntax.


protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>()
        .HasKey(p => p.ProductId); // Explicitly define primary key

    modelBuilder.Entity<Product>()
        .Property(p => p.Name)
        .IsRequired()
        .HasMaxLength(100); // Configure property constraints

    modelBuilder.Entity<Product>()
        .HasOne(p => p.Category) // Configure one-to-many relationship
        .WithMany(c => c.Products)
        .HasForeignKey(p => p.CategoryId);
}
                

Data Annotations

You can also use data annotations as attributes directly on your entity classes.


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

public class Product
{
    [Key] // Data annotation for primary key
    public int ProductId { get; set; }

    [Required] // Data annotation for not null
    [StringLength(100)] // Data annotation for max length
    public string Name { get; set; }

    [Column(TypeName = "decimal(18, 2)")] // Specify database type
    public decimal Price { get; set; }

    [ForeignKey("Category")] // Data annotation for foreign key
    public int CategoryId { get; set; }
    public Category Category { get; set; }
}
                
Note: It's generally recommended to use the Fluent API for configurations that involve relationships or advanced mappings, as it keeps your entity classes cleaner. Data annotations are useful for simple property-level configurations.

Key Concepts Recap

  • Entities: Classes representing database tables.
  • Properties: Map to database columns.
  • Primary Keys: Uniquely identify entities. EF Core conventions often auto-detect them.
  • Foreign Keys: Establish relationships between tables.
  • Navigation Properties: Allow traversal between related entities.
  • Fluent API: Powerful configuration via code.
  • Data Annotations: Attribute-based configuration.

Next Steps

Continue exploring how to leverage EF Core for efficient data access and management in your .NET applications. Consider learning about: