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:
- Entities: Plain Old CLR Objects (POCOs) that represent data in your application.
- DbSets: Collections of entities exposed through your
DbContext
. - Relationships: How entities relate to each other (one-to-one, one-to-many, many-to-many).
- Primary Keys: Unique identifiers for entities.
- Foreign Keys: Properties that establish relationships between entities.
- Value Types: Properties that are typically stored as columns in the entity's table.
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:
- Entity Discovery: Any public class with a public `get` and `set` accessor for its properties is considered a potential entity. Classes that are derived from
DbContext
are scanned forDbSet<TEntity>
properties, and the generic type parameters are treated as entities. - Primary Key Discovery: Properties named
Id
or[EntityName]Id
(e.g.,BlogId
) are recognized as primary keys. - Navigation Properties: Properties that represent relationships (e.g., a
List<Post>
on aBlog
entity) are recognized as navigation properties. - Foreign Key Discovery: Properties named
[ReferencedEntityName]Id
(e.g.,BlogId
on aPost
entity) are recognized as foreign keys.
Example: Convention-Based Model
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:
[Key]
: Explicitly marks a property as the primary key.[Required]
: Makes a property non-nullable in the database.[StringLength(int)]
: Sets the maximum length of a string column.[Column(name: "")]
: Specifies the name of the database column.[ForeignKey("PropertyName")]
: Explicitly defines a foreign key property.
Example: Model with Data Annotations
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:
- Entity-to-table mappings.
- Property-to-column mappings (including data types, nullability, etc.).
- Relationships and their constraints.
- Indexes.
- Primary and foreign keys.
Example: Model with Fluent API
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:
- Conventions: Ideal for simple models where default EF Core conventions align with your needs. It reduces boilerplate code.
- Data Annotations: Useful for adding explicit constraints and mappings directly to your entity classes, keeping configuration close to the domain logic.
- Fluent API: Provides the most control and is recommended for complex scenarios, when conventions are insufficient, or when you prefer to keep database-specific configurations separate from your entity classes.
Often, a combination of conventions and Fluent API (or conventions and data annotations) is used to achieve the desired model.
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.