Entity Framework Core - Code First Approach With Fluent API

In this article, you will learn about the code first approach with Fluent API.

Entity Framework Core provides two approaches to map database tables with our entity classes - Code First and Database First. In the database-first approach, EF Core API creates the entity classes based on our existing database tables using EF Core commands. But the more recommended approach to work with EF Core is the code-first approach. In the code-first approach, EF Core API creates the database and tables using migration based on the conventions and configuration provided in your entity classes.
 
In this article, we will learn about EF Core code-first approach with Fluent API. This article assumes that you have a basic knowledge of Entity Framework code-first approach.
 
Consider the following entity classes - Customer and Product.
  1. public class Customer  
  2. {  
  3.     public int Id { getset; }  
  4.     public string Name { getset; }  
  5. }  
  6.   
  7. public class Product  
  8. {  
  9.     public int Id { getset; }  
  10.     public string Description { getset; }  
  11.     public double Price { getset; }  
  12. }  
If we apply migrations on this model and scaffold the database, EF Core will create two tables, Customers and Products, in the database with column names similar to that of the class property names. If we examine the tables, we will see that Id columns in the tables are set as the Primary Keys. This is because EF Core by convention will mark the columns with name as Id as primary keys. We can also notice that string properties in the entity classes are mapped to NVARCHAR(MAX) types in the database and the fields are nullable too.
 
This is one of the drawbacks of the convention-based approach of EF Core. We don't have control over how to map our entity classes to the columns. To achieve this, EF Core provides configuration options to customize our entity to table mapping by overriding default conventions. Configuring entities to the database can be achieved in two ways - Data Annotation Attributes and Fluent API.
 
In this article, I will be explaining the Fluent API configuration. Note that I will be trying to keep this article very lean and simple just to provide a basic understanding of fluent API configuration and some clean practices while using fluent API.
 
For using Fluent API, we need to override the OnModelCreating method in our DbContext class. We need to extend the ModelBuilder parameter of the method to configure our mappings. So, if I need to make the Name property of the Customer entity to have a NOT NULL column in the database with a max length of 255 characters, we need to configure the mapping in the OnModelCreating as below.
  1. public class CustomerDBContext: DbContext   
  2. {  
  3.     public DbSet<Customer> Customers { getset; }  
  4.           
  5.     public DbSet<Product> Products { getset; }  
  6.   
  7.     protected override void OnModelCreating(ModelBuilder modelBuilder)  
  8.     {  
  9.         //Write Fluent API configurations here  
  10.   
  11.         modelBuilder.Entity<Customer>()  
  12.                 .Property(c => c.Name)  
  13.                 .IsRequired()  
  14.                 .HasMaxLength(255);  
  15.     }  
  16. }  
If we have similar configurations for the Product entity, we need to write its fluent configurations in the OnModelCreating method. This leads to a problem if we have a lot of entity classes. Our OnModelCreating method will become very large and unmaintainable very quick. To solve this issue, EF Core provides an interface IEntityTypeConfiguration. So, for each entity class in our codebase, we can create a separate configuration class which contains its fluent configuration by implementing this interface. This interface provides a method Configure and while implementing this interface, we can provide our fluent configuration for the entity in this method. Our customer and product configurations are given below.
  1. public class CustomerConfiguration : IEntityTypeConfiguration<Customer>  
  2. {  
  3.     public void Configure(EntityTypeBuilder<Customer> builder)  
  4.     {  
  5.         builder.HasKey(c => c.Id);  
  6.           
  7.         builder.Property(c => c.Name)  
  8.             .IsRequired()  
  9.             .HasMaxLength(55);  
  10.     }  
  11. }  
  12.   
  13. public class ProductConfiguration : IEntityTypeConfiguration<Product>  
  14. {  
  15.     public void Configure(EntityTypeBuilder<Product> builder)  
  16.     {  
  17.         builder.HasKey(c => c.Id);  
  18.           
  19.         builder.Property(c => c.Description)  
  20.             .IsRequired()  
  21.             .HasMaxLength(500);  
  22.     }  
  23. }  
Now, we can remove the Fluent configuration we wrote in the OnModelCreating method. We can replace the current code with the following code in the OnModelCreating method to take the entity configurations from our configuration classes.
  1. protected override void OnModelCreating(ModelBuilder modelBuilder)  
  2. {  
  3.     modelBuilder.ApplyConfiguration(new CustomerConfiguration());  
  4.     modelBuilder.ApplyConfiguration(new ProductConfiguration());  
  5. }  
Still a small problem remains. We need to add the line modelBuilder.ApplyConfiguration(new EntityConfiguration())  for all our configuration classes in our model. We could write some code using reflection to overcome this but EF Core actually takes care of this too.
 
We can use the ApplyConfigurationsFromAssemblly method in the ModelBuilder class to achieve this. So our OnModelCreating method will look like the below code.
  1. protected override void OnModelCreating(ModelBuilder modelBuilder)  
  2. {  
  3.    modelBuilder.ApplyConfigurationsFromAssembly(typeof(CustomerConfiguration).Assembly);   
  4.    // Pass any of the entity type configuration class as the parameter of typeof().  
  5.    // EF core will scan the assembly containing that class for finding out the remaining entity configurations
  6. }  

Summary

 
In this article, we looked at EF Core code-first approach using Fluent API configurations. We have also looked at Entity type configuration classes to separate the configuration logic to separate classes which made our code cleaner.