Entity Framework Core - Code First Approach With Fluent API

Entity Framework Core - Code First Approach With Fluent API

Entity Framework (EF) Core is a lightweight, extensible, open source and cross-platform version of the popular Entity Framework data access technology from Microsoft. EF 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 post, we will learn about the EF Core code first approach using Fluent API. I assume that you have basic knowledge of the Entity Framework code first approach.

Consider the following entity classes - Customer and Product.

public class Customer  
{  
    public int Id { get; set; }  
    public string Name { get; set; }  
}  

public class Product  
{  
    public int Id { get; set; }  
    public string Description { get; set; }  
    public double Price { get; set; }  
}

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 the Id columns in the tables are set as the Primary Keys. This is because EF Core by convention will mark the columns with the name 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 overcome this limitation, 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 Configuration.

In this post, I will explain the Fluent API configuration. Note that I will be trying to keep this post very lean and simple just to provide a basic understanding of Fluent API configuration and some clean practices while using Fluent API configuration.

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 is as below.

public class CustomerDBContext: DbContext   
{  
    public DbSet<Customer> Customers { get; set; }  

    public DbSet<Product> Products { get; set; }  

    protected override void OnModelCreating(ModelBuilder modelBuilder)  
    {  
        //Write Fluent API configurations here  

        modelBuilder.Entity<Customer>()  
                .Property(c => c.Name)  
                .IsRequired()  
                .HasMaxLength(255);  
    }  
}

Similarly for the Product entity, we need to write its Fluent configurations in the OnModelCreating method. This leads to a problem if we have a large number of entity classes. Our OnModelCreating method will become very large and unmaintainable very quickly. It will be nice if we could extract the configuration logic for each entity to a separate class adhering to Single Responsibility Principle. We can achieve this by using the interface IEntityTypeConfiguration provided by EF Core. So, for each entity class in our codebase, we can create a separate configuration class that contains its Fluent configuration by implementing this interface. This interface provides a method named Configure and while implementing this interface, we can provide our Fluent configuration for the entity in this method.

Our customer and product configurations extracted to separate classes are given below.

public class CustomerConfiguration : IEntityTypeConfiguration<Customer>  
{  
    public void Configure(EntityTypeBuilder<Customer> builder)  
    {  
        builder.HasKey(c => c.Id);  

        builder.Property(c => c.Name)  
            .IsRequired()  
            .HasMaxLength(55);  
    }  
}  

public class ProductConfiguration : IEntityTypeConfiguration<Product>  
{  
    public void Configure(EntityTypeBuilder<Product> builder)  
    {  
        builder.HasKey(c => c.Id);  

        builder.Property(c => c.Description)  
            .IsRequired()  
            .HasMaxLength(500);  
    }  
}

Now, we can remove the Fluent configuration code we wrote in the OnModelCreating method. We can update the OnModelCreating method with the following code to take the entity configurations from our configuration classes.

protected override void OnModelCreating(ModelBuilder modelBuilder)  
{  
    modelBuilder.ApplyConfiguration(new CustomerConfiguration());  
    modelBuilder.ApplyConfiguration(new ProductConfiguration());  
}

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 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.

protected override void OnModelCreating(ModelBuilder modelBuilder)  
{  
   modelBuilder.ApplyConfigurationsFromAssembly(typeof(CustomerConfiguration).Assembly);   
   // Pass any of the entity type configuration class as the parameter of typeof().  
   // EF core will scan the assembly containing that class for finding out the remaining entity configurations
}

Summary

In this post, 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.