Configuring mapping between Database Table(s) and Data Model Class(es) using Fluent API

Description Article
Summary

The Fluent API is an alternative way to DataAnnotations to configure domain classes mapping. It is a reacher methodology since it provides more functionalities for configuration than DataAnnotations.

Description

Types of Mappings

Mappings To Database
Model-wide Mapping
  • Set default Schema
  • Set Custom Conventions
Entity Mapping
  • To Single or Multiple Tables and Schema
  • To Complex type
  • Inheritance Hierarchies
Property Mapping
  • To Column, Column Name, Column Type, Nullable or Not Null Column, Column size, Columns Order
  • To Concurrency column
  • To Foreign key column
  • To configure relationships

How To Do It

Fluent mapping is configured on the overridable method OnModelCreating() on the derived class of DbContext, through the DbModelBuilder class instance passed as parameter.

This DbModelBuilder class is the responsible to map the POCO classes to a database schema.

When the method is invoked by Entity Framework, the Entity Data Model (EDM) is automatically created and the domain classes are initialized:

  • The model builder allows control over how Entity Framework represents the database, one of the features of the model builder allows for control over how FKs in the database are translated to Navigation Properties in the EDM.
public class BreakAwayContext: DbContext
    {
        public BreakAwayContext(): base()
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Standard> Standards { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            //Configure domain classes using modelBuilder here

            base.OnModelCreating(modelBuilder);
        }
    }

Configuration is performed by entity either by:

Option 1: Step by step, calling multiple times the DbModelBuilder.Entity<TEntityType>/ComplexType<TComplexType> methods:

public DbSet<Student> Students { get; set; }

public DbSet<Book> Books { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Student>().HasKey<int>(e => e.StudentID);

  modelBuilder.Entity<Student>()
    .Property(e => e.StudentName)
    .HasColumnName("Name")
    .HasMaxLength(50);
  //...

  modelBuilder.Entity<Book>()
    .Property(e => e.Photo).HasColumnType("image");
  modelBuilder.Entity<Book>()
    .Property(e => n.BookName)
    .IsRequired()
    .HasMaxLength(200);
  //...

  base.OnModelCreating(modelBuilder);
}

Option 2: Grouping configuration per entity within individual derived classes from EntityTypeConfiguration or ComplexTypeConfiguration
and then call them through the DbModelBuilder.Configurations.Add/AddFromAssembly methods:

public class StudentConfiguration : EntityTypeConfiguration<Student>
{
  public StudentConfiguration()
  {
    HasKey<int>(e => e.StudentID);
    Property(e => e.StudentName).HasColumnName("Name").HasMaxLength(50);
    //...
  }
}

public class BookConfiguration : EntityTypeConfiguration<Book>
{
  public BookConfiguration()
  {
    Property(e => e.Photo).HasColumnType("image");
    Property(e => e.BookName).IsRequired().HasMaxLength(200);
    //...
  }
}

Note: These configuration classes can be grouped in a “Mapping layer” in case of a multi-tier application.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Configurations.Add(new StudentConfiguration());
  modelBuilder.Configurations.Add(new BookConfiguration());
}

In case of a generic base context class:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
                          .Where(type => !String.IsNullOrEmpty(type.Namespace))
                          .Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));  

  foreach (var type in typesToRegister)
  {
    dynamic configurationInstance = Activator.CreateInstance(type);
    modelBuilder.Configurations.Add(configurationInstance);
  }  

  base.OnModelCreating(modelBuilder);
}

Detailed Description

The EntityTypeConfiguration class provides important methods to configure entities and its properties to override various Code First conventions.

It is obtained by calling the Entity<TEntity>() method of DbModelBuilder class.

The most important methods are described next:

 

Method Name Return Type Description
HasKey<TKey> EntityTypeConfiguration Configures the primary key property(s) for this entity type.
HasMany<TTargetEntity> ManyNavigationPropertyConfiguration Configures a many-to-many relationship from this entity type.
Returns a result type ManyNavigationPropertyConfiguration<TEntityType, TTargetEntity> and allows then to configure if a navigation property is expected or not on the other side of the relationship
Returns a result type ManyNavigationPropertyConfiguration<TEntityType, TTargetEntity> and allows then to configure if a navigation property is expected or not on the other side of the relationship
HasOptional<TTargetEntity> OptionalNavigationPropertyConfiguration Configures an optional relationship from this entity type. Instances of the entity type will be able to be saved to the database without this relationship being specified. The foreign key in the database will be nullable.
HasRequired<TTargetEntity> RequiredNavigationPropertyConfiguration Configures a required relationship from this entity type. Instances of the entity type will not be able to be saved to the database unless this relationship is specified. The foreign key in the database will be non-nullable.
Returns a result type RequiredNavigationPropertyConfiguration<TEntityType, TTargetEntity> and allows then to configure if a navigation property is expected or not on the other side of the relationship
Ignore<TProperty> Void Excludes a property from the model so that it will not be mapped to the database.
Map EntityTypeConfiguration Allows advanced configuration related to how this entity type is mapped to the database schema.
Property<T> StructuralTypeConfiguration Configures a struct property that is defined on this type.
ToTable Void Configures the table name that this entity type is mapped to.

The property mappings configuration can be performed through the Entity<TEntity>.Property<T>(Expression<Func<TStructuralType, T>>) method.

 

 

Note

Fluent API can also be used with DataAnnotation at the same time. Code First gives precedence to Fluent API.

References

[1] Refer to this article for further information on Fluent API on Code First