Using Fluent API in Entity Framework Core Code First

by GeeksArray

In this blogpost explains how to configure entities using Fluent API of Entity Framework Core or EF6 above versions for the Code First approach. This will give you details about how to configure business rules with Fluent API methods for PrimaryKey, Unique, Index, Required, Identity, etc.

Fluent API is used to configure data models to override conventions. It is based on the Fluent API design pattern where results are captured using method chaining.

You can configure data models using Data Annotations or Fluent API. Data Annotations gives the only subset of configurations, Fluent API provides full set of configuration options available for code first.

For this tutorial, we will use data models created using EF Core Code First Migration with separate assembly This creates two different domain classes which will be configured using Fluent API in this tutorial.

The most common way to implement Fluent API is by overriding the OnModelCreating method of your derived class by DBContext. IF you have followed tutorial from the above link open StoreContext class.

public class StoreContext : DbContext
{
public StoreContext(DbContextOptions options) 
        : base(options)
{

}

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

#region properties
public DbSet Categories { get; set; }
public DbSet Products { get; set; }
#endregion
}

Fluent API Configurations

  1. HasDefaultSchema

    HasDefaultSchema is model wide configuration. It allows you to configure default database schema to be used for tables. This schema name used for all database objects which do not have explicit schema name.

    modelBuilder.HasDefaultSchema("sales");
    
  2. ToTable

    ToTable() Fluent API method will configure table name. By default Entity Framework 6 creates a table with convention <DbSet<TEntity> property name> + 's' (or 'es') Entity Framework Core will create table as DbSet<TEntity>

    modelBuilder.Entity<Category>()
        .ToTable("GeekCategories");
    
  3. Configure Primary Key using HasKey

    By default Entity Framework will create Primary Key for property having name as ID or <class> + ID. To explicitly configure Primary key you can use HasKey method. It also creates a clustered index on the column.

    modelBuilder.Entity<Category>()
        .HasKey(t => t.CategoryID);
    
  4. Composite Primary Key

    If you need to configure Primary Key with more than one column of the table.

     modelBuilder.Entity<Product>()
        .HasKey(t => new { t.CategoryID, t.ProductID });
    
  5. Switching on/off identity

    Following example switch off identity values, the application has to create values for these primary key columns.

    modelBuilder.Entity<Product>()
        .Property(p => p.ProductId)
        .ValueGeneratedNever();
    

    Following example switch on identity columns. So that database generates values for primary column.

    modelBuilder.Entity<Product>()
        .Property(p => p.ProductId)
        .ValueGeneratedOnAdd();
    
  6. Maximum Length

    It is necessary to set Maximum length for each string column to avoid truncation or business rules violation. If application tries to insert/update string length with more than configured length Entity Framework will throw DbEntityValidationException.

    Following code sets Maximum length for ProductName column as 50 characters.

    modelBuilder.Entity<Product>()
        .Property(t => t.ProductName).HasMaxLength(50);
    
  7. Required

    When columns must have values you can mark those columns as NOT NULL. Entity Framework code first should configure such properties with the IsRequired() method. If application tries to insert/update record with NULL values Entity Framework throws DbEntityValidationException.

    Following code marks ProductName column as required

    modelBuilder.Entity<Product>()
        .Property(t => t.ProductName).IsRequired();
    

    Fluent API allows you to chain different methods so you can configure MaxLength and Required as shown:

    modelBuilder.Entity<Product>()
        .Property(t => t.ProductName).HasMaxLength(50)
        .IsRequired();
    
  8. Creating Index

    By using Fluent API with Entity Framework you can create clustered or non clustered index for columns. The following code creates non unique index on the ProductName column.

    modelBuilder.Entity<Product>()
        .HasIndex(t => t.ProductName);
    

    You can also create an index on multiple columns.

    modelBuilder.Entity<Product>()
        .HasIndex(t => new { t.ProductName, t.Discontinued });
    

    Following code creates Unique index on ProductName column.

    modelBuilder.Entity<Product<()
        .HasIndex(t => t.ProductName)
        .IsUnique();
    
  9. Configure Not Mapped columns

    Columns that are computed or dependant on other columns like DiscountAmount which might maybe dependant on Price and CustomerType to calculate final amount of discount.

    Fluent API Ignore method can be use to configure Not mapped columns. The following column configures DiscountAmount column as not to be mapped.

    modelBuilder.Entity<Product>()
        .Ignore(t => t.DiscountAmount);
    
  10. Configure CLR property to specific column of Database

    If your database column name needs to be different than the property name of the Data Model you can use the HasColumnName to configure column name.

    modelBuilder.Entity<Product>()
        .Property(t => t.ProductName)
        .HasColumnName("ProductName");
    
  11. Unicode strings

    To manage strings in an international database or in a language other than English you will have to use unicode strings to store data. For example, you need to add details of Japnese products in Japnese language characters.

    modelBuilder.Entity<Product>()
        .Property(t => t.ProductName)
        .IsUnicode(true);
    
  12. DataType

    You can explicitly mention the DataType of database column irrespective of CLR property types.

    modelBuilder.Entity<Product>()
        .Property(p => p.ProductID)
        .HasColumnType("bigint");
    
  13. Concurrency Token

    For setting a property of entity to represent concurrency token you can use Fluent API IsConcurrencyToken() method.

    modelBuilder.Entity<Product>()
        .Property(t => t.ProductName)
        .IsConcurrencyToken();
    

    Here is the real time scenario of Concurrency. User1 and User2 are reading details about the product having ProductName as "Product 1".

    User1 changes ProductName from "Product 1" to "Product 2", then User2 tries to change ProductName as "Product 3" however User2 did not read or do not have the latest committed changes Entity Framework throws DbConcurrencyException.

    In this case, Entity Framework will add additional WHERE Clause for column ProductName. So when User2 tries to update same product it's query becomes as shown below which will return zero records because User1 already committed ProductName as "Product 2".

    UPDATE Products
    SET ProductName = 'Product 3'
    WHERE ProductID = 3 and ProductName = 'Product 1' 
    
  14. Splitting entity to multiple tables

    You can have a one entity class that maps to multiple tables from the same database . For example, Product data model will have details for Product as well as it's inventory details. However at database level Products and Product inventory details needs to be in separate tables.

    The following code will create two different tables using Code First. First with name Product and it will have columns ProductID, ProductName, CategoryID. The second will be created with name ProductInventory with columns t.ProductID, t.QuantityPerUnit, t.UnitPrice, t.UnitsInStock, t.ReorderLevel.

    modelBuilder.Entity<Product>()
    .Map(m =>
    {
    m.Properties(t => new { t.ProductID, t.ProductName, t.CategoryID });
    m.ToTable("Product");
    })
    .Map(m =>
    {
    m.Properties(t => new { t.ProductID, t.QuantityPerUnit, t.UnitPrice,
        t.UnitsInStock, t.ReorderLevel });
    m.ToTable("ProductInventory");
    });
    
  15. IsFixedLength

    Fluent API method IsFixedLength allows you to configure properties to create columns with fixed lengths. The application must provide value with the exact number of characters as mentioned in MaxLength. Space will be added if the length of value is less than the configured value of MaxLength.

    modelBuilder.Entity<Product>()
        .Property(t => t.ProductCode).IsFixedLength(true)
        .HasMaxLength(3);
    

    The length of all ProductCode value must be 3 characters.


Source code on Git hub for using fluent api in efcore code first Models and DBContext classes on Github

Speak your mind
Please login to post your comment!