Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Entity Framework: Separate Entity Class Mapping using Fluent API

5.00/5 (4 votes)
6 Nov 2018CPOL1 min read 14.4K  
Separate entity class mapping using Fluent API

Introduction

Separating entity class mapping using Fluent API will make modification and maintenance easier for midiup or large application/project.

Background

When working with lot of Entity classes in a project, it is difficult to maintain all of the Entity configuration in DbContext's OnModelCreating method. For example:

C#
public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext()
        : base()
    {
    }

    public DbSet<Trainees> Trainees { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }
}

Now if you have hundreds of Entities, then you have to add all of the entities here which makes modification and maintenance much harder.

Moreover, if you want to override the relationship, Indexing or field property mapping, you may modify the OnModelCreating method in the following way:

C#
        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<Trainee>().ToTable("Trainee");
            builder.Entity<Trainee>().HasKey(t => t.Id).Property
                (t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            builder.Entity<Trainee>().Property(t => t.TraineeNo).
                             HasMaxLength(10).IsRequired().IsUnicode();

            //HasRequired(t=>t.Batch)
            //    .WithMany(b=>b.Trainees)
            //    .HasForeignKey(t=>t.BatchId);

            builder.Entity<Trainee>().Property(t => t.Gender);
            builder.Entity<Trainee>().Property(t => t.Name).HasMaxLength(500).IsRequired();
            builder.Entity<Trainee>().Property(t => t.FatherName).HasMaxLength(500);
            builder.Entity<Trainee>().Property(t => t.MotherName).HasMaxLength(500);
            builder.Entity<Trainee>().Property(t => t.NationalId).HasMaxLength(50);

            builder.Entity<Trainee>().Property(t => t.MobilNo).HasMaxLength(50);
            builder.Entity<Trainee>().Property(t => t.PholeNo).HasMaxLength(50);
            builder.Entity<Trainee>().Property(t => t.Email).HasMaxLength(150);
            builder.Entity<Trainee>().Property(t => t.LastEducation).HasMaxLength(50);
            builder.Entity<Trainee>().Property(t => t.TraineeStatus);
            builder.Entity<Trainee>().Property(t => t.RetakeAvailable);
            builder.Entity<Trainee>().Property(t => t.Note).HasMaxLength(750);
            builder.Entity<Trainee>().Property(t => t.Deleted);
            builder.Entity<Trainee>().Property(t => t.DeleteDate);

            builder.Entity<Trainee>().HasMany(pr => pr.TraineeRoles)
                .WithMany(cr => cr.Trainees)
                .Map(m => m.ToTable("Trainee_TraineeRole_Mapping"));

            base.OnModelCreating(builder);
            // Customize the ASP.NET Identity model and override the defaults if needed.
            // For example, you can rename the ASP.NET Identity table names and more.
            // Add your customizations after calling base.OnModelCreating(builder);
        }

So we can customize Entity by EntityTypeConfiguration class. But it is horrible to maintain when it crosses 10 to 20 entities.

That's why we need to separate each entity configuration in different cs class files.

Using the Code

Create a CS class named TraineeMap.cs. It will inherit EntityTypeConfiguration. For EntityTypeConfiguration, you need to use System.Data.Entity.ModelConfiguration namespace.

C#
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;
using TIS.Entity.Participant;

namespace TIS.Data.Mapping.Participant
{
    public class TraineeMap : EntityTypeConfiguration<Trainee>
    {
        public TraineeMap()
        {
            ToTable("Trainee");
            HasKey(t => t.Id).Property(t => t.Id).HasDatabaseGeneratedOption
                                                  (DatabaseGeneratedOption.Identity);
            Property(t => t.TraineeNo).HasMaxLength(10).IsRequired().IsUnicode();

            //HasRequired(t=>t.Batch)
            //    .WithMany(b=>b.Trainees)
            //    .HasForeignKey(t=>t.BatchId);

            Property(t => t.Gender);
            Property(t => t.Name).HasMaxLength(500).IsRequired();
            Property(t => t.FatherName).HasMaxLength(500);
            Property(t => t.MotherName).HasMaxLength(500);
            Property(t => t.NationalId).HasMaxLength(50);

            Property(t => t.MobilNo).HasMaxLength(50);
            Property(t => t.PholeNo).HasMaxLength(50);
            Property(t => t.Email).HasMaxLength(150);
            Property(t => t.LastEducation).HasMaxLength(50);
            Property(t => t.TraineeStatus);
            Property(t => t.RetakeAvailable);
            Property(t => t.Note).HasMaxLength(750);
            Property(t => t.Deleted);
            Property(t => t.DeleteDate);

            this.HasMany(pr => pr.TraineeRoles)
                .WithMany(cr => cr.Trainees)
                .Map(m => m.ToTable("Trainee_TraineeRole_Mapping"));
        }
    }
}

Now change the OnModelCreating method as follows:

C#
protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Configurations.Add(new TraineeMap());

    base.OnModelCreating(builder);
    // Customize the ASP.NET Identity model and override the defaults if needed.
    // For example, you can rename the ASP.NET Identity table names and more.
    // Add your customizations after calling base.OnModelCreating(builder);
}

Again, the problem here is that for each Entity, we have to repeat the following line:

C#
builder.Configurations.Add(new {EntityClassName}());

How can we eliminate redundancy? Reflection may help us by finding which classes are derived from EntityTypeConfiguration generic class.

C#
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);
}

Above, we are filtering from assembly and finding those classes whose type is typeof(EntityTypeConfiguration<>) and then adding to configuration.

Points of Interest

Separating each entity mapping makes modification and maintenance easier. By applying Reflection, the automation is fulfilled.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)